From 7749ec424ce0da77a960f15e3a4dce4499ea15a3 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Thu, 25 Jun 2026 10:45:56 +0200 Subject: [PATCH 1/8] feat(2602): add a message when search results come up empty --- .gitignore | 1 + Gemfile | 1 + Gemfile.lock | 12 + app/components/table_components/table.rb | 4 + app/views/chapters/index.html.erb | 2 +- app/views/groups/index.html.erb | 2 +- app/views/organizations/index.html.erb | 2 +- app/views/students/index.html.erb | 2 +- config/database.yml | 2 + config/locales/en.yml | 5 +- db/structure.sql | 453 +++++++++--------- .../components/table_components/table_spec.rb | 43 ++ 12 files changed, 294 insertions(+), 235 deletions(-) create mode 100644 spec/components/table_components/table_spec.rb diff --git a/.gitignore b/.gitignore index 71ec0f9d1..981d28584 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ yarn-debug.log* /app/assets/builds/* !/app/assets/builds/.keep +prod_db.sql \ No newline at end of file diff --git a/Gemfile b/Gemfile index 926b0ad30..80d7eb598 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ gem 'stimulus-rails' gem 'tailwindcss-rails' gem 'tiddle' gem 'turbo-rails' +gem 'tzinfo-data' gem 'view_component' group :production do diff --git a/Gemfile.lock b/Gemfile.lock index 89493259b..217a6961d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,6 +175,7 @@ GEM net-http (~> 0.5) fastimage (2.2.4) ffi (1.17.4) + ffi (1.17.4-x64-mingw-ucrt) fog-aws (3.33.2) base64 (>= 0.2, < 0.4) fog-core (~> 2.6) @@ -198,6 +199,9 @@ GEM google-protobuf (4.33.4) bigdecimal rake (>= 13) + google-protobuf (4.33.4-x64-mingw-ucrt) + bigdecimal + rake (>= 13) has_scope (0.9.0) actionpack (>= 7.0) activesupport (>= 7.0) @@ -279,6 +283,8 @@ GEM nokogiri (1.19.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) + nokogiri (1.19.3-x64-mingw-ucrt) + racc (~> 1.4) oauth2 (2.0.18) faraday (>= 0.17.3, < 4.0) jwt (>= 1.0, < 4.0) @@ -313,6 +319,7 @@ GEM ast (~> 2.4.1) racc pg (1.6.3) + pg (1.6.3-x64-mingw-ucrt) pg_query (6.2.2) google-protobuf (>= 3.25.3) pg_search (2.3.7) @@ -498,6 +505,7 @@ GEM railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) tailwindcss-ruby (4.1.16) + tailwindcss-ruby (4.1.16-x64-mingw-ucrt) thor (1.5.0) tiddle (1.8.1) activerecord (>= 6.1.0) @@ -509,6 +517,8 @@ GEM railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + tzinfo-data (1.2026.2) + tzinfo (>= 1.0.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) @@ -539,6 +549,7 @@ GEM PLATFORMS ruby + x64-mingw-ucrt DEPENDENCIES SyslogLogger @@ -602,6 +613,7 @@ DEPENDENCIES tailwindcss-rails tiddle turbo-rails + tzinfo-data view_component webmock diff --git a/app/components/table_components/table.rb b/app/components/table_components/table.rb index 52a972172..7c09a2a25 100644 --- a/app/components/table_components/table.rb +++ b/app/components/table_components/table.rb @@ -16,8 +16,12 @@ class TableComponents::Table < ViewComponent::Base <%= render TableComponents::Column.with_collection(@row_component::columns(**@column_arguments), order_scope_name: @order_scope_name) %> <% if @options[:turbo_id] %>
<% end %> <%= render @row_component.with_collection(@rows, pagy: @pagy, **@row_arguments) %> + <% if @rows.empty? && @options[:empty_message] %> +
<%= @options[:empty_message] %>
+ <% end %> + ERB # rubocop:disable Metrics/ParameterLists diff --git a/app/views/chapters/index.html.erb b/app/views/chapters/index.html.erb index 66e026e38..3c1ac835e 100644 --- a/app/views/chapters/index.html.erb +++ b/app/views/chapters/index.html.erb @@ -8,7 +8,7 @@
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @chapters, row_component: TableComponents::ChapterRow) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @chapters, row_component: TableComponents::ChapterRow,options: { empty_message: t(:no_chapters_found) }) do |t| t.with_left do %>
diff --git a/app/views/groups/index.html.erb b/app/views/groups/index.html.erb index a92e3ec64..5f5acac22 100644 --- a/app/views/groups/index.html.erb +++ b/app/views/groups/index.html.erb @@ -7,7 +7,7 @@ ].compact) %>
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @groups, row_component: TableComponents::GroupRow) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @groups, row_component: TableComponents::GroupRow,options: { empty_message: t(:no_groups_found) }) do |t| t.with_left do %>
diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb index b2e4e14a6..9d6425258 100644 --- a/app/views/organizations/index.html.erb +++ b/app/views/organizations/index.html.erb @@ -8,7 +8,7 @@
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @organizations, row_component: TableComponents::OrganizationRow) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @organizations, row_component: TableComponents::OrganizationRow,options: { empty_message: t(:no_organizations_found) }) do |t| t.with_left do %>
diff --git a/app/views/students/index.html.erb b/app/views/students/index.html.erb index df34e63cb..801eee9ab 100644 --- a/app/views/students/index.html.erb +++ b/app/views/students/index.html.erb @@ -7,7 +7,7 @@ (CommonComponents::ButtonComponent.new(label: t(:add_student), href: new_student_path) if policy(Student).new?) ].compact) %> -<%= render TableComponents::Table.new(pagy: @pagy, rows: @student_rows, row_component: TableComponents::StudentRow) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @student_rows, row_component: TableComponents::StudentRow, options: { empty_message: t(:no_students_found) }) do |t| t.with_left do %>
diff --git a/config/database.yml b/config/database.yml index a253baef9..cb77f735d 100644 --- a/config/database.yml +++ b/config/database.yml @@ -3,6 +3,8 @@ default: &default pool: 5 timeout: 5000 host: <%= ENV['DATABASE_HOST'] || 'localhost' %> + # port: 5433 + port: 5432 username: <%= ENV['DATABASE_USER'] || 'tracker' %> password: <%= ENV['DATABASE_PASSWORD'] || 'tracker' %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 9dc1b2a63..e701535b3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -357,6 +357,10 @@ en: health_insurance: Health Insurance health_issues: Health Issues hiv_tested: HIV Tested + no_students_found: "No Students found" + no_groups_found: "No Groups found" + no_chapters_found: "No Chapters found" + no_organizations_found: "No Organizations found" confirm: Confirm import: Import @@ -451,7 +455,6 @@ en: one: MLID other: MLID - errors: messages: extension_whitelist_error: File is not an image diff --git a/db/structure.sql b/db/structure.sql index 9d53667dd..addd1f06a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -24,20 +24,6 @@ CREATE EXTENSION IF NOT EXISTS btree_gist WITH SCHEMA public; COMMENT ON EXTENSION btree_gist IS 'support for indexing common datatypes in GiST'; --- --- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: - --- - -CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public; - - --- --- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: - --- - -COMMENT ON EXTENSION pg_stat_statements IS 'track execution statistics of all SQL statements executed'; - - -- -- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - -- @@ -249,8 +235,8 @@ SET default_table_access_method = heap; CREATE TABLE public.ar_internal_metadata ( key character varying NOT NULL, value character varying, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -273,6 +259,7 @@ CREATE TABLE public.assignments ( -- CREATE SEQUENCE public.assignments_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -309,6 +296,7 @@ CREATE TABLE public.authentication_tokens ( -- CREATE SEQUENCE public.authentication_tokens_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -364,6 +352,7 @@ CREATE TABLE public.chapters ( -- CREATE SEQUENCE public.chapters_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -395,7 +384,7 @@ CREATE TABLE public.deleted_lessons ( -- CREATE TABLE public.enrollments ( - id uuid DEFAULT public.gen_random_uuid() NOT NULL, + id uuid DEFAULT gen_random_uuid() NOT NULL, student_id bigint NOT NULL, group_id bigint NOT NULL, active_since date NOT NULL, @@ -425,6 +414,7 @@ CREATE TABLE public.grade_descriptors ( -- CREATE SEQUENCE public.grade_descriptors_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -461,6 +451,7 @@ CREATE TABLE public.grades ( -- CREATE SEQUENCE public.grades_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -534,6 +525,7 @@ CREATE TABLE public.groups ( -- CREATE SEQUENCE public.groups_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -562,27 +554,6 @@ SELECT NULL::integer AS subject_id; --- --- Name: lesson_table_rows; Type: VIEW; Schema: public; Owner: - --- - -CREATE VIEW public.lesson_table_rows AS -SELECT - NULL::integer AS group_id, - NULL::date AS date, - NULL::timestamp without time zone AS created_at, - NULL::timestamp without time zone AS updated_at, - NULL::integer AS subject_id, - NULL::timestamp without time zone AS deleted_at, - NULL::uuid AS id, - NULL::character varying AS group_name, - NULL::character varying AS chapter_name, - NULL::character varying AS subject_name, - NULL::bigint AS group_student_count, - NULL::bigint AS graded_student_count, - NULL::numeric AS average_mark; - - -- -- Name: lessons; Type: TABLE; Schema: public; Owner: - -- @@ -674,8 +645,8 @@ CREATE TABLE public.roles ( name character varying, resource_type character varying, resource_id integer, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL ); @@ -804,6 +775,7 @@ CREATE TABLE public.organizations ( -- CREATE SEQUENCE public.organizations_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -840,6 +812,7 @@ SELECT -- CREATE SEQUENCE public.roles_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -883,6 +856,7 @@ CREATE TABLE public.skills ( -- CREATE SEQUENCE public.skills_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -946,6 +920,7 @@ CREATE TABLE public.student_images ( -- CREATE SEQUENCE public.student_images_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1104,6 +1079,7 @@ CREATE TABLE public.student_tags ( -- CREATE SEQUENCE public.students_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1138,6 +1114,7 @@ SELECT -- CREATE SEQUENCE public.subjects_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1157,7 +1134,7 @@ ALTER SEQUENCE public.subjects_id_seq OWNED BY public.subjects.id; -- CREATE TABLE public.tags ( - id uuid DEFAULT public.gen_random_uuid() NOT NULL, + id uuid DEFAULT gen_random_uuid() NOT NULL, tag_name character varying NOT NULL, organization_id bigint NOT NULL, shared boolean NOT NULL, @@ -1171,6 +1148,7 @@ CREATE TABLE public.tags ( -- CREATE SEQUENCE public.users_id_seq + AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1510,20 +1488,6 @@ CREATE INDEX index_chapters_on_organization_id ON public.chapters USING btree (o CREATE INDEX index_deleted_lessons_on_group_id ON public.deleted_lessons USING btree (group_id); --- --- Name: index_deleted_lessons_on_lesson_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX index_deleted_lessons_on_lesson_id ON public.deleted_lessons USING btree (lesson_id); - - --- --- Name: index_deleted_lessons_on_subject_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_deleted_lessons_on_subject_id ON public.deleted_lessons USING btree (subject_id); - - -- -- Name: index_enrollments_on_group_dates_and_student; Type: INDEX; Schema: public; Owner: - -- @@ -1601,6 +1565,13 @@ CREATE INDEX index_lessons_on_group_id ON public.lessons USING btree (group_id); CREATE UNIQUE INDEX index_lessons_on_group_id_and_subject_id_and_date ON public.lessons USING btree (group_id, subject_id, date) WHERE (deleted_at IS NULL); +-- +-- Name: index_lessons_on_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_lessons_on_id ON public.lessons USING btree (id); + + -- -- Name: index_lessons_on_subject_id; Type: INDEX; Schema: public; Owner: - -- @@ -1622,6 +1593,13 @@ CREATE INDEX index_roles_on_name ON public.roles USING btree (name); CREATE INDEX index_roles_on_name_and_resource_type_and_resource_id ON public.roles USING btree (name, resource_type, resource_id); +-- +-- Name: index_roles_on_resource_type_and_resource_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_roles_on_resource_type_and_resource_id ON public.roles USING btree (resource_type, resource_id); + + -- -- Name: index_skills_on_organization_id; Type: INDEX; Schema: public; Owner: - -- @@ -1699,6 +1677,20 @@ CREATE INDEX index_tags_on_organization_id ON public.tags USING btree (organizat CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email); +-- +-- Name: index_users_roles_on_role_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_users_roles_on_role_id ON public.users_roles USING btree (role_id); + + +-- +-- Name: index_users_roles_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_users_roles_on_user_id ON public.users_roles USING btree (user_id); + + -- -- Name: index_users_roles_on_user_id_and_role_id; Type: INDEX; Schema: public; Owner: - -- @@ -1706,6 +1698,95 @@ CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email); CREATE INDEX index_users_roles_on_user_id_and_role_id ON public.users_roles USING btree (user_id, role_id); +-- +-- Name: chapter_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.chapter_summaries AS + SELECT c.id, + c.chapter_name, + c.mlid AS chapter_mlid, + o.mlid AS organization_mlid, + concat(o.mlid, '-', c.mlid) AS full_mlid, + c.organization_id, + o.organization_name, + c.deleted_at, + (count(DISTINCT + CASE + WHEN (g.deleted_at IS NULL) THEN g.id + ELSE NULL::integer + END))::integer AS group_count, + (count(DISTINCT + CASE + WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN s.id + ELSE NULL::integer + END))::integer AS student_count, + c.created_at, + c.updated_at + FROM ((((public.chapters c + LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) + LEFT JOIN public.enrollments en ON ((en.group_id = g.id))) + LEFT JOIN public.students s ON ((s.id = en.student_id))) + LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) + GROUP BY c.id, o.id; + + +-- +-- Name: group_lesson_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.group_lesson_summaries AS + SELECT slu.lesson_id, + slu.lesson_date, + gr.id AS group_id, + gr.chapter_id, + slu.subject_id, + concat(gr.group_name, ' - ', c.chapter_name) AS group_chapter_name, + (round(avg(slu.average_mark), 2))::double precision AS average_mark, + (sum(slu.grade_count))::bigint AS grade_count, + (round((((sum( + CASE + WHEN (slu.grade_count = 0) THEN 0 + ELSE 1 + END))::numeric / (count(slu.*))::numeric) * (100)::numeric), 2))::double precision AS attendance + FROM ((public.student_lesson_summaries slu + JOIN public.groups gr ON ((slu.group_id = gr.id))) + JOIN public.chapters c ON ((gr.chapter_id = c.id))) + WHERE (slu.deleted_at IS NULL) + GROUP BY slu.lesson_id, gr.id, c.id, slu.subject_id, slu.lesson_date + ORDER BY slu.lesson_date; + + +-- +-- Name: group_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.group_summaries AS + SELECT g.id, + g.group_name, + g.deleted_at, + g.created_at, + g.chapter_id, + c.chapter_name, + o.id AS organization_id, + o.mlid AS organization_mlid, + c.mlid AS chapter_mlid, + g.mlid, + concat(o.mlid, '-', c.mlid, '-', g.mlid) AS full_mlid, + o.organization_name, + sum( + CASE + WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN 1 + ELSE 0 + END) AS student_count + FROM ((((public.groups g + LEFT JOIN public.enrollments en ON ((g.id = en.group_id))) + LEFT JOIN public.students s ON ((s.id = en.student_id))) + LEFT JOIN public.chapters c ON ((g.chapter_id = c.id))) + LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) + GROUP BY g.id, c.id, o.id; + + -- -- Name: lesson_skill_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- @@ -1725,6 +1806,40 @@ CREATE OR REPLACE VIEW public.lesson_skill_summaries AS GROUP BY l.id, sk.id, su.id; +-- +-- Name: organization_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.organization_summaries AS + SELECT o.id, + o.organization_name, + o.mlid AS organization_mlid, + (count(DISTINCT + CASE + WHEN ((c.id IS NOT NULL) AND (c.deleted_at IS NULL)) THEN c.id + ELSE NULL::integer + END))::integer AS chapter_count, + (count(DISTINCT + CASE + WHEN ((g.id IS NOT NULL) AND (g.deleted_at IS NULL)) THEN g.id + ELSE NULL::integer + END))::integer AS group_count, + (count(DISTINCT + CASE + WHEN ((s.id IS NOT NULL) AND (s.deleted_at IS NULL)) THEN s.id + ELSE NULL::integer + END))::integer AS student_count, + o.country, + o.updated_at, + o.created_at, + o.deleted_at + FROM (((public.organizations o + LEFT JOIN public.chapters c ON ((c.organization_id = o.id))) + LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) + LEFT JOIN public.students s ON ((s.organization_id = o.id))) + GROUP BY o.id; + + -- -- Name: performance_per_group_per_skill_per_lessons _RETURN; Type: RULE; Schema: public; Owner: - -- @@ -1750,26 +1865,22 @@ CREATE OR REPLACE VIEW public.performance_per_group_per_skill_per_lessons AS -- --- Name: subject_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- Name: student_analytics_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- -CREATE OR REPLACE VIEW public.subject_summaries AS - SELECT su.id, - su.subject_name, - su.organization_id, - sum( - CASE - WHEN (a.deleted_at IS NOT NULL) THEN 0 - ELSE 1 - END) AS skill_count, - su.created_at, - su.updated_at, - su.deleted_at - FROM ((public.subjects su - LEFT JOIN public.assignments a ON ((su.id = a.subject_id))) - LEFT JOIN public.skills sk ON ((sk.id = a.skill_id))) - WHERE (sk.deleted_at IS NULL) - GROUP BY su.id; +CREATE OR REPLACE VIEW public.student_analytics_summaries AS + SELECT s.id, + s.organization_id, + s.first_name, + s.last_name, + s.old_group_id, + COALESCE(array_agg(en.group_id) FILTER (WHERE (en.group_id IS NOT NULL)), '{}'::bigint[]) AS enrolled_group_ids + FROM ((public.students s + JOIN public.organizations o ON ((s.organization_id = o.id))) + JOIN public.enrollments en ON ((s.id = en.student_id))) + WHERE (s.deleted_at IS NULL) + GROUP BY s.id, s.first_name, s.last_name + ORDER BY s.last_name, s.first_name; -- @@ -1793,30 +1904,32 @@ CREATE OR REPLACE VIEW public.student_averages AS JOIN public.lessons l ON (((l.id = g.lesson_id) AND (l.subject_id = su.id)))) GROUP BY s.id, su.id, sk.skill_name; + -- --- Name: group_lesson_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- Name: student_lesson_details _RETURN; Type: RULE; Schema: public; Owner: - -- -CREATE OR REPLACE VIEW public.group_lesson_summaries AS - SELECT slu.lesson_id, - slu.lesson_date, - gr.id AS group_id, - gr.chapter_id, - slu.subject_id, - concat(gr.group_name, ' - ', c.chapter_name) AS group_chapter_name, - (round(avg(slu.average_mark), 2))::double precision AS average_mark, - (sum(slu.grade_count))::bigint AS grade_count, - (round((((sum( - CASE - WHEN (slu.grade_count = 0) THEN 0 - ELSE 1 - END))::numeric / (count(slu.*))::numeric) * (100)::numeric), 2))::double precision AS attendance - FROM ((public.student_lesson_summaries slu - JOIN public.groups gr ON ((slu.group_id = gr.id))) - JOIN public.chapters c ON ((gr.chapter_id = c.id))) - WHERE (slu.deleted_at IS NULL) - GROUP BY slu.lesson_id, gr.id, c.id, slu.subject_id, slu.lesson_date - ORDER BY slu.lesson_date; +CREATE OR REPLACE VIEW public.student_lesson_details AS + SELECT s.id AS student_id, + s.first_name, + s.last_name, + s.deleted_at AS student_deleted_at, + l.id AS lesson_id, + l.date, + l.deleted_at AS lesson_deleted_at, + l.subject_id, + round(avg(g.mark), 2) AS average_mark, + count(g.mark) AS grade_count, + COALESCE(jsonb_object_agg(g.skill_id, jsonb_build_object('mark', g.mark, 'grade_descriptor_id', g.grade_descriptor_id, 'skill_name', sk.skill_name)) FILTER (WHERE (sk.skill_name IS NOT NULL)), '{}'::jsonb) AS skill_marks + FROM ((((public.students s + JOIN public.enrollments en ON ((s.id = en.student_id))) + JOIN public.lessons l ON ((en.group_id = l.group_id))) + LEFT JOIN public.grades g ON (((g.student_id = s.id) AND (g.lesson_id = l.id) AND (g.deleted_at IS NULL)))) + LEFT JOIN public.skills sk ON ((sk.id = g.skill_id))) + WHERE ((en.active_since <= l.date) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= l.date))) + GROUP BY s.id, l.id + ORDER BY l.subject_id; + -- -- Name: student_lesson_summaries _RETURN; Type: RULE; Schema: public; Owner: - @@ -1873,146 +1986,26 @@ CREATE OR REPLACE VIEW public.student_tag_table_rows AS -- --- Name: group_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- Name: subject_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- -CREATE OR REPLACE VIEW public.group_summaries AS - SELECT g.id, - g.group_name, - g.deleted_at, - g.created_at, - g.chapter_id, - c.chapter_name, - o.id AS organization_id, - o.mlid AS organization_mlid, - c.mlid AS chapter_mlid, - g.mlid, - concat(o.mlid, '-', c.mlid, '-', g.mlid) AS full_mlid, - o.organization_name, +CREATE OR REPLACE VIEW public.subject_summaries AS + SELECT su.id, + su.subject_name, + su.organization_id, sum( CASE - WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN 1 - ELSE 0 - END) AS student_count - FROM ((((public.groups g - LEFT JOIN public.enrollments en ON ((g.id = en.group_id))) - LEFT JOIN public.students s ON ((s.id = en.student_id))) - LEFT JOIN public.chapters c ON ((g.chapter_id = c.id))) - LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) - GROUP BY g.id, c.id, o.id; - - --- --- Name: student_lesson_details _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.student_lesson_details AS - SELECT s.id AS student_id, - s.first_name, - s.last_name, - s.deleted_at AS student_deleted_at, - l.id AS lesson_id, - l.date, - l.deleted_at AS lesson_deleted_at, - l.subject_id, - round(avg(g.mark), 2) AS average_mark, - count(g.mark) AS grade_count, - COALESCE(jsonb_object_agg(g.skill_id, jsonb_build_object('mark', g.mark, 'grade_descriptor_id', g.grade_descriptor_id, 'skill_name', sk.skill_name)) FILTER (WHERE (sk.skill_name IS NOT NULL)), '{}'::jsonb) AS skill_marks - FROM ((((public.students s - JOIN public.enrollments en ON ((s.id = en.student_id))) - JOIN public.lessons l ON ((en.group_id = l.group_id))) - LEFT JOIN public.grades g ON (((g.student_id = s.id) AND (g.lesson_id = l.id) AND (g.deleted_at IS NULL)))) - LEFT JOIN public.skills sk ON ((sk.id = g.skill_id))) - WHERE ((en.active_since <= l.date) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= l.date))) - GROUP BY s.id, l.id - ORDER BY l.subject_id; - - --- --- Name: student_analytics_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.student_analytics_summaries AS - SELECT s.id, - s.organization_id, - s.first_name, - s.last_name, - s.old_group_id, - COALESCE(array_agg(en.group_id) FILTER (WHERE (en.group_id IS NOT NULL)), '{}'::bigint[]) AS enrolled_group_ids - FROM ((public.students s - JOIN public.organizations o ON ((s.organization_id = o.id))) - JOIN public.enrollments en ON ((s.id = en.student_id))) - WHERE (s.deleted_at IS NULL) - GROUP BY s.id, s.first_name, s.last_name - ORDER BY s.last_name, s.first_name; - - --- --- Name: chapter_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.chapter_summaries AS - SELECT c.id, - c.chapter_name, - c.mlid AS chapter_mlid, - o.mlid AS organization_mlid, - concat(o.mlid, '-', c.mlid) AS full_mlid, - c.organization_id, - o.organization_name, - c.deleted_at, - (count(DISTINCT - CASE - WHEN (g.deleted_at IS NULL) THEN g.id - ELSE NULL::integer - END))::integer AS group_count, - (count(DISTINCT - CASE - WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN s.id - ELSE NULL::integer - END))::integer AS student_count, - c.created_at, - c.updated_at - FROM ((((public.chapters c - LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) - LEFT JOIN public.enrollments en ON ((en.group_id = g.id))) - LEFT JOIN public.students s ON ((s.id = en.student_id))) - LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) - GROUP BY c.id, o.id; - --- --- Name: organization_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.organization_summaries AS - SELECT o.id, - o.organization_name, - o.mlid AS organization_mlid, - (count(DISTINCT - CASE - WHEN ((c.id IS NOT NULL) AND (c.deleted_at IS NULL)) THEN c.id - ELSE NULL::integer - END))::integer AS chapter_count, - (count(DISTINCT - CASE - WHEN ((g.id IS NOT NULL) AND (g.deleted_at IS NULL)) THEN g.id - ELSE NULL::integer - END))::integer AS group_count, - (count(DISTINCT - CASE - WHEN ((c.id IS NOT NULL) AND (c.deleted_at IS NULL)) THEN c.student_count - ELSE 0 - WHEN ((s.id IS NOT NULL) AND (s.deleted_at IS NULL)) THEN s.id - ELSE NULL::integer - END))::integer AS student_count, - o.country, - o.updated_at, - o.created_at, - o.deleted_at - FROM (((public.organizations o - LEFT JOIN public.chapters c ON ((c.organization_id = o.id))) - LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) - LEFT JOIN public.students s ON ((s.organization_id = o.id))) - GROUP BY o.id; + WHEN (a.deleted_at IS NOT NULL) THEN 0 + ELSE 1 + END) AS skill_count, + su.created_at, + su.updated_at, + su.deleted_at + FROM ((public.subjects su + LEFT JOIN public.assignments a ON ((su.id = a.subject_id))) + LEFT JOIN public.skills sk ON ((sk.id = a.skill_id))) + WHERE (sk.deleted_at IS NULL) + GROUP BY su.id; -- diff --git a/spec/components/table_components/table_spec.rb b/spec/components/table_components/table_spec.rb new file mode 100644 index 000000000..93cf3f2fb --- /dev/null +++ b/spec/components/table_components/table_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +# Minimal stub to test TableComponents::Table in isolation without policy concerns +class StubTableRow < ViewComponent::Base + with_collection_parameter :item + + def self.columns(**_) + [{ column_name: 'Name' }] + end + + def initialize(item:, item_counter:, pagy:) + @item = item + end + + erb_template '
<%= @item %>
' +end + +RSpec.describe TableComponents::Table, type: :component do + let(:row_component) { StubTableRow } + + context 'when rows is empty' do + it 'renders the empty message when the empty_message option is provided' do + render_inline(described_class.new(rows: [], row_component: row_component, options: { empty_message: 'No items found' })) + + expect(page).to have_text('No items found') + end + + it 'does not render an empty message when no empty_message option is provided' do + render_inline(described_class.new(rows: [], row_component: row_component)) + + expect(page).not_to have_css('[style*="grid-column: 1/-1"]') + end + end + + context 'when rows is not empty' do + it 'does not render the empty message' do + render_inline(described_class.new(rows: ['Alice'], row_component: row_component, options: { empty_message: 'No items found' })) + + expect(page).not_to have_text('No items found') + expect(page).to have_css('.stub-row', text: 'Alice') + end + end +end From e58173222cf930b9aa08e578572d5e11648d1856 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Thu, 25 Jun 2026 11:54:13 +0200 Subject: [PATCH 2/8] add linux Tailwind binary --- Gemfile.lock | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 217a6961d..9528fe480 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -176,6 +176,7 @@ GEM fastimage (2.2.4) ffi (1.17.4) ffi (1.17.4-x64-mingw-ucrt) + ffi (1.17.4-x86_64-linux-gnu) fog-aws (3.33.2) base64 (>= 0.2, < 0.4) fog-core (~> 2.6) @@ -202,6 +203,9 @@ GEM google-protobuf (4.33.4-x64-mingw-ucrt) bigdecimal rake (>= 13) + google-protobuf (4.33.4-x86_64-linux-gnu) + bigdecimal + rake (>= 13) has_scope (0.9.0) actionpack (>= 7.0) activesupport (>= 7.0) @@ -285,6 +289,8 @@ GEM racc (~> 1.4) nokogiri (1.19.3-x64-mingw-ucrt) racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-gnu) + racc (~> 1.4) oauth2 (2.0.18) faraday (>= 0.17.3, < 4.0) jwt (>= 1.0, < 4.0) @@ -320,6 +326,7 @@ GEM racc pg (1.6.3) pg (1.6.3-x64-mingw-ucrt) + pg (1.6.3-x86_64-linux) pg_query (6.2.2) google-protobuf (>= 3.25.3) pg_search (2.3.7) @@ -506,6 +513,7 @@ GEM tailwindcss-ruby (~> 4.0) tailwindcss-ruby (4.1.16) tailwindcss-ruby (4.1.16-x64-mingw-ucrt) + tailwindcss-ruby (4.1.16-x86_64-linux-gnu) thor (1.5.0) tiddle (1.8.1) activerecord (>= 6.1.0) @@ -550,6 +558,7 @@ GEM PLATFORMS ruby x64-mingw-ucrt + x86_64-linux DEPENDENCIES SyslogLogger From 90a89e9bb29f64775e2396d4c2a74f320ab3fc62 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Thu, 25 Jun 2026 14:21:12 +0200 Subject: [PATCH 3/8] fix test cases for returning empty message when table results are not found --- Gemfile | 5 ++++- config/database.yml | 3 +-- spec/components/table_components/table_spec.rb | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 80d7eb598..05d1893be 100644 --- a/Gemfile +++ b/Gemfile @@ -68,11 +68,14 @@ group :development, :test do gem 'webmock' end +group :development, :test do + gem 'dotenv-rails' +end + group :development do gem 'annotate' gem 'better_errors' gem 'binding_of_caller' - gem 'dotenv-rails' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'pry' diff --git a/config/database.yml b/config/database.yml index cb77f735d..ff86a8993 100644 --- a/config/database.yml +++ b/config/database.yml @@ -3,8 +3,7 @@ default: &default pool: 5 timeout: 5000 host: <%= ENV['DATABASE_HOST'] || 'localhost' %> - # port: 5433 - port: 5432 + port: <%= ENV['DATABASE_PORT'] || 5432 %> username: <%= ENV['DATABASE_USER'] || 'tracker' %> password: <%= ENV['DATABASE_PASSWORD'] || 'tracker' %> diff --git a/spec/components/table_components/table_spec.rb b/spec/components/table_components/table_spec.rb index 93cf3f2fb..2f52d85c0 100644 --- a/spec/components/table_components/table_spec.rb +++ b/spec/components/table_components/table_spec.rb @@ -8,8 +8,9 @@ def self.columns(**_) [{ column_name: 'Name' }] end - def initialize(item:, item_counter:, pagy:) + def initialize(item:, **_) @item = item + super() end erb_template '
<%= @item %>
' From 485603a305ffa8d2a9dcc7af8e623661713ab327 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Thu, 25 Jun 2026 14:30:09 +0200 Subject: [PATCH 4/8] fix linting errors on double inclusion of groups --- Gemfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 05d1893be..9f349299c 100644 --- a/Gemfile +++ b/Gemfile @@ -46,6 +46,7 @@ end group :development, :test do gem 'bullet', '!= 6.0.0' # 6.0.0 seems to break with Turbolinks + gem 'dotenv-rails' gem 'rails-controller-testing' gem 'rubocop', require: false gem 'rubocop-performance' @@ -68,10 +69,6 @@ group :development, :test do gem 'webmock' end -group :development, :test do - gem 'dotenv-rails' -end - group :development do gem 'annotate' gem 'better_errors' From 115732bc1202141c583216bfc072c853a1ce8e47 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Tue, 7 Jul 2026 12:17:08 +0200 Subject: [PATCH 5/8] feat(2602): remove Gemfile changes for windows - Change empty_message function - Change styling to match our convention - remove extra lines --- .gitignore | 3 +- Gemfile | 3 +- Gemfile.lock | 23 +- app/components/table_components/table.rb | 14 +- app/views/chapters/index.html.erb | 2 +- app/views/groups/index.html.erb | 2 +- app/views/organizations/index.html.erb | 2 +- config/database.yml | 3 +- config/locales/en.yml | 1 + db/structure.sql | 453 +++++++++--------- .../components/table_components/table_spec.rb | 13 +- 11 files changed, 257 insertions(+), 262 deletions(-) diff --git a/.gitignore b/.gitignore index 981d28584..28617fb93 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,4 @@ yarn-debug.log* .yarn-integrity /app/assets/builds/* -!/app/assets/builds/.keep -prod_db.sql \ No newline at end of file +!/app/assets/builds/.keep \ No newline at end of file diff --git a/Gemfile b/Gemfile index 9f349299c..926b0ad30 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,6 @@ gem 'stimulus-rails' gem 'tailwindcss-rails' gem 'tiddle' gem 'turbo-rails' -gem 'tzinfo-data' gem 'view_component' group :production do @@ -46,7 +45,6 @@ end group :development, :test do gem 'bullet', '!= 6.0.0' # 6.0.0 seems to break with Turbolinks - gem 'dotenv-rails' gem 'rails-controller-testing' gem 'rubocop', require: false gem 'rubocop-performance' @@ -73,6 +71,7 @@ group :development do gem 'annotate' gem 'better_errors' gem 'binding_of_caller' + gem 'dotenv-rails' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock index 29952d830..dc22c577b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -175,8 +175,6 @@ GEM net-http (~> 0.5) fastimage (2.2.4) ffi (1.17.4) - ffi (1.17.4-x64-mingw-ucrt) - ffi (1.17.4-x86_64-linux-gnu) fog-aws (3.33.2) base64 (>= 0.2, < 0.4) fog-core (~> 2.6) @@ -200,12 +198,6 @@ GEM google-protobuf (4.33.4) bigdecimal rake (>= 13) - google-protobuf (4.33.4-x64-mingw-ucrt) - bigdecimal - rake (>= 13) - google-protobuf (4.33.4-x86_64-linux-gnu) - bigdecimal - rake (>= 13) has_scope (0.9.0) actionpack (>= 7.0) activesupport (>= 7.0) @@ -287,10 +279,6 @@ GEM nokogiri (1.19.4) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.19.3-x64-mingw-ucrt) - racc (~> 1.4) - nokogiri (1.19.3-x86_64-linux-gnu) - racc (~> 1.4) oauth2 (2.0.18) faraday (>= 0.17.3, < 4.0) jwt (>= 1.0, < 4.0) @@ -325,8 +313,6 @@ GEM ast (~> 2.4.1) racc pg (1.6.3) - pg (1.6.3-x64-mingw-ucrt) - pg (1.6.3-x86_64-linux) pg_query (6.2.2) google-protobuf (>= 3.25.3) pg_search (2.3.7) @@ -512,9 +498,7 @@ GEM tailwindcss-rails (4.6.0) railties (>= 7.0.0) tailwindcss-ruby (~> 4.0) - tailwindcss-ruby (4.1.16) - tailwindcss-ruby (4.1.16-x64-mingw-ucrt) - tailwindcss-ruby (4.1.16-x86_64-linux-gnu) + tailwindcss-ruby (4.3.1) thor (1.5.0) tiddle (1.8.1) activerecord (>= 6.1.0) @@ -526,8 +510,6 @@ GEM railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2026.2) - tzinfo (>= 1.0.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) @@ -558,8 +540,6 @@ GEM PLATFORMS ruby - x64-mingw-ucrt - x86_64-linux DEPENDENCIES SyslogLogger @@ -623,7 +603,6 @@ DEPENDENCIES tailwindcss-rails tiddle turbo-rails - tzinfo-data view_component webmock diff --git a/app/components/table_components/table.rb b/app/components/table_components/table.rb index 7c09a2a25..41006d0ac 100644 --- a/app/components/table_components/table.rb +++ b/app/components/table_components/table.rb @@ -14,16 +14,14 @@ class TableComponents::Table < ViewComponent::Base
<%= render TableComponents::Column.with_collection(@row_component::columns(**@column_arguments), order_scope_name: @order_scope_name) %> - <% if @options[:turbo_id] %>
<% end %> + <% if @options[:turbo_id] %>
<% end %> <%= render @row_component.with_collection(@rows, pagy: @pagy, **@row_arguments) %> - <% if @rows.empty? && @options[:empty_message] %> -
<%= @options[:empty_message] %>
+ <% if @rows.empty? && empty_message %> +
<%= empty_message %>
<% end %>
- ERB - # rubocop:disable Metrics/ParameterLists def initialize(row_component:, rows:, column_arguments: {}, row_arguments: {}, order_scope_name: :table_order, pagy: nil, options: {}) @row_component = row_component @@ -39,4 +37,10 @@ def initialize(row_component:, rows:, column_arguments: {}, row_arguments: {}, o def grid_columns "grid-template-columns: repeat(#{@row_component.columns(**@column_arguments).count}, #{@options[:wrap] ? 'minmax(max-content, auto)' : 'auto'})" end + + def empty_message + return if @options[:empty_message] == false + + @options[:empty_message] || I18n.t(:no_results_found) + end end diff --git a/app/views/chapters/index.html.erb b/app/views/chapters/index.html.erb index 3c1ac835e..80a5865d1 100644 --- a/app/views/chapters/index.html.erb +++ b/app/views/chapters/index.html.erb @@ -8,7 +8,7 @@
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @chapters, row_component: TableComponents::ChapterRow,options: { empty_message: t(:no_chapters_found) }) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @chapters, row_component: TableComponents::ChapterRow, options: { empty_message: t(:no_chapters_found) }) do |t| t.with_left do %>
diff --git a/app/views/groups/index.html.erb b/app/views/groups/index.html.erb index 5f5acac22..22319e18e 100644 --- a/app/views/groups/index.html.erb +++ b/app/views/groups/index.html.erb @@ -7,7 +7,7 @@ ].compact) %>
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @groups, row_component: TableComponents::GroupRow,options: { empty_message: t(:no_groups_found) }) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @groups, row_component: TableComponents::GroupRow, options: { empty_message: t(:no_groups_found) }) do |t| t.with_left do %>
diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb index 9d6425258..bd5de017a 100644 --- a/app/views/organizations/index.html.erb +++ b/app/views/organizations/index.html.erb @@ -8,7 +8,7 @@
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @organizations, row_component: TableComponents::OrganizationRow,options: { empty_message: t(:no_organizations_found) }) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @organizations, row_component: TableComponents::OrganizationRow, : { empty_message: t(:no_organizations_found) }) do |t| t.with_left do %>
diff --git a/config/database.yml b/config/database.yml index ff86a8993..d505a1da9 100644 --- a/config/database.yml +++ b/config/database.yml @@ -3,10 +3,9 @@ default: &default pool: 5 timeout: 5000 host: <%= ENV['DATABASE_HOST'] || 'localhost' %> - port: <%= ENV['DATABASE_PORT'] || 5432 %> username: <%= ENV['DATABASE_USER'] || 'tracker' %> password: <%= ENV['DATABASE_PASSWORD'] || 'tracker' %> - + port: 5433 development: <<: *default database: tracker_development diff --git a/config/locales/en.yml b/config/locales/en.yml index e701535b3..a9060b8d1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -361,6 +361,7 @@ en: no_groups_found: "No Groups found" no_chapters_found: "No Chapters found" no_organizations_found: "No Organizations found" + no_results_found: No results found confirm: Confirm import: Import diff --git a/db/structure.sql b/db/structure.sql index addd1f06a..9d53667dd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -24,6 +24,20 @@ CREATE EXTENSION IF NOT EXISTS btree_gist WITH SCHEMA public; COMMENT ON EXTENSION btree_gist IS 'support for indexing common datatypes in GiST'; +-- +-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA public; + + +-- +-- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION pg_stat_statements IS 'track execution statistics of all SQL statements executed'; + + -- -- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - -- @@ -235,8 +249,8 @@ SET default_table_access_method = heap; CREATE TABLE public.ar_internal_metadata ( key character varying NOT NULL, value character varying, - created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL ); @@ -259,7 +273,6 @@ CREATE TABLE public.assignments ( -- CREATE SEQUENCE public.assignments_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -296,7 +309,6 @@ CREATE TABLE public.authentication_tokens ( -- CREATE SEQUENCE public.authentication_tokens_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -352,7 +364,6 @@ CREATE TABLE public.chapters ( -- CREATE SEQUENCE public.chapters_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -384,7 +395,7 @@ CREATE TABLE public.deleted_lessons ( -- CREATE TABLE public.enrollments ( - id uuid DEFAULT gen_random_uuid() NOT NULL, + id uuid DEFAULT public.gen_random_uuid() NOT NULL, student_id bigint NOT NULL, group_id bigint NOT NULL, active_since date NOT NULL, @@ -414,7 +425,6 @@ CREATE TABLE public.grade_descriptors ( -- CREATE SEQUENCE public.grade_descriptors_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -451,7 +461,6 @@ CREATE TABLE public.grades ( -- CREATE SEQUENCE public.grades_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -525,7 +534,6 @@ CREATE TABLE public.groups ( -- CREATE SEQUENCE public.groups_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -554,6 +562,27 @@ SELECT NULL::integer AS subject_id; +-- +-- Name: lesson_table_rows; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.lesson_table_rows AS +SELECT + NULL::integer AS group_id, + NULL::date AS date, + NULL::timestamp without time zone AS created_at, + NULL::timestamp without time zone AS updated_at, + NULL::integer AS subject_id, + NULL::timestamp without time zone AS deleted_at, + NULL::uuid AS id, + NULL::character varying AS group_name, + NULL::character varying AS chapter_name, + NULL::character varying AS subject_name, + NULL::bigint AS group_student_count, + NULL::bigint AS graded_student_count, + NULL::numeric AS average_mark; + + -- -- Name: lessons; Type: TABLE; Schema: public; Owner: - -- @@ -645,8 +674,8 @@ CREATE TABLE public.roles ( name character varying, resource_type character varying, resource_id integer, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL + created_at timestamp without time zone, + updated_at timestamp without time zone ); @@ -775,7 +804,6 @@ CREATE TABLE public.organizations ( -- CREATE SEQUENCE public.organizations_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -812,7 +840,6 @@ SELECT -- CREATE SEQUENCE public.roles_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -856,7 +883,6 @@ CREATE TABLE public.skills ( -- CREATE SEQUENCE public.skills_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -920,7 +946,6 @@ CREATE TABLE public.student_images ( -- CREATE SEQUENCE public.student_images_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1079,7 +1104,6 @@ CREATE TABLE public.student_tags ( -- CREATE SEQUENCE public.students_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1114,7 +1138,6 @@ SELECT -- CREATE SEQUENCE public.subjects_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1134,7 +1157,7 @@ ALTER SEQUENCE public.subjects_id_seq OWNED BY public.subjects.id; -- CREATE TABLE public.tags ( - id uuid DEFAULT gen_random_uuid() NOT NULL, + id uuid DEFAULT public.gen_random_uuid() NOT NULL, tag_name character varying NOT NULL, organization_id bigint NOT NULL, shared boolean NOT NULL, @@ -1148,7 +1171,6 @@ CREATE TABLE public.tags ( -- CREATE SEQUENCE public.users_id_seq - AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE @@ -1488,6 +1510,20 @@ CREATE INDEX index_chapters_on_organization_id ON public.chapters USING btree (o CREATE INDEX index_deleted_lessons_on_group_id ON public.deleted_lessons USING btree (group_id); +-- +-- Name: index_deleted_lessons_on_lesson_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_deleted_lessons_on_lesson_id ON public.deleted_lessons USING btree (lesson_id); + + +-- +-- Name: index_deleted_lessons_on_subject_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_deleted_lessons_on_subject_id ON public.deleted_lessons USING btree (subject_id); + + -- -- Name: index_enrollments_on_group_dates_and_student; Type: INDEX; Schema: public; Owner: - -- @@ -1565,13 +1601,6 @@ CREATE INDEX index_lessons_on_group_id ON public.lessons USING btree (group_id); CREATE UNIQUE INDEX index_lessons_on_group_id_and_subject_id_and_date ON public.lessons USING btree (group_id, subject_id, date) WHERE (deleted_at IS NULL); --- --- Name: index_lessons_on_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX index_lessons_on_id ON public.lessons USING btree (id); - - -- -- Name: index_lessons_on_subject_id; Type: INDEX; Schema: public; Owner: - -- @@ -1593,13 +1622,6 @@ CREATE INDEX index_roles_on_name ON public.roles USING btree (name); CREATE INDEX index_roles_on_name_and_resource_type_and_resource_id ON public.roles USING btree (name, resource_type, resource_id); --- --- Name: index_roles_on_resource_type_and_resource_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_roles_on_resource_type_and_resource_id ON public.roles USING btree (resource_type, resource_id); - - -- -- Name: index_skills_on_organization_id; Type: INDEX; Schema: public; Owner: - -- @@ -1677,20 +1699,6 @@ CREATE INDEX index_tags_on_organization_id ON public.tags USING btree (organizat CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email); --- --- Name: index_users_roles_on_role_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_users_roles_on_role_id ON public.users_roles USING btree (role_id); - - --- --- Name: index_users_roles_on_user_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_users_roles_on_user_id ON public.users_roles USING btree (user_id); - - -- -- Name: index_users_roles_on_user_id_and_role_id; Type: INDEX; Schema: public; Owner: - -- @@ -1698,95 +1706,6 @@ CREATE INDEX index_users_roles_on_user_id ON public.users_roles USING btree (use CREATE INDEX index_users_roles_on_user_id_and_role_id ON public.users_roles USING btree (user_id, role_id); --- --- Name: chapter_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.chapter_summaries AS - SELECT c.id, - c.chapter_name, - c.mlid AS chapter_mlid, - o.mlid AS organization_mlid, - concat(o.mlid, '-', c.mlid) AS full_mlid, - c.organization_id, - o.organization_name, - c.deleted_at, - (count(DISTINCT - CASE - WHEN (g.deleted_at IS NULL) THEN g.id - ELSE NULL::integer - END))::integer AS group_count, - (count(DISTINCT - CASE - WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN s.id - ELSE NULL::integer - END))::integer AS student_count, - c.created_at, - c.updated_at - FROM ((((public.chapters c - LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) - LEFT JOIN public.enrollments en ON ((en.group_id = g.id))) - LEFT JOIN public.students s ON ((s.id = en.student_id))) - LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) - GROUP BY c.id, o.id; - - --- --- Name: group_lesson_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.group_lesson_summaries AS - SELECT slu.lesson_id, - slu.lesson_date, - gr.id AS group_id, - gr.chapter_id, - slu.subject_id, - concat(gr.group_name, ' - ', c.chapter_name) AS group_chapter_name, - (round(avg(slu.average_mark), 2))::double precision AS average_mark, - (sum(slu.grade_count))::bigint AS grade_count, - (round((((sum( - CASE - WHEN (slu.grade_count = 0) THEN 0 - ELSE 1 - END))::numeric / (count(slu.*))::numeric) * (100)::numeric), 2))::double precision AS attendance - FROM ((public.student_lesson_summaries slu - JOIN public.groups gr ON ((slu.group_id = gr.id))) - JOIN public.chapters c ON ((gr.chapter_id = c.id))) - WHERE (slu.deleted_at IS NULL) - GROUP BY slu.lesson_id, gr.id, c.id, slu.subject_id, slu.lesson_date - ORDER BY slu.lesson_date; - - --- --- Name: group_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.group_summaries AS - SELECT g.id, - g.group_name, - g.deleted_at, - g.created_at, - g.chapter_id, - c.chapter_name, - o.id AS organization_id, - o.mlid AS organization_mlid, - c.mlid AS chapter_mlid, - g.mlid, - concat(o.mlid, '-', c.mlid, '-', g.mlid) AS full_mlid, - o.organization_name, - sum( - CASE - WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN 1 - ELSE 0 - END) AS student_count - FROM ((((public.groups g - LEFT JOIN public.enrollments en ON ((g.id = en.group_id))) - LEFT JOIN public.students s ON ((s.id = en.student_id))) - LEFT JOIN public.chapters c ON ((g.chapter_id = c.id))) - LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) - GROUP BY g.id, c.id, o.id; - - -- -- Name: lesson_skill_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- @@ -1806,40 +1725,6 @@ CREATE OR REPLACE VIEW public.lesson_skill_summaries AS GROUP BY l.id, sk.id, su.id; --- --- Name: organization_summaries _RETURN; Type: RULE; Schema: public; Owner: - --- - -CREATE OR REPLACE VIEW public.organization_summaries AS - SELECT o.id, - o.organization_name, - o.mlid AS organization_mlid, - (count(DISTINCT - CASE - WHEN ((c.id IS NOT NULL) AND (c.deleted_at IS NULL)) THEN c.id - ELSE NULL::integer - END))::integer AS chapter_count, - (count(DISTINCT - CASE - WHEN ((g.id IS NOT NULL) AND (g.deleted_at IS NULL)) THEN g.id - ELSE NULL::integer - END))::integer AS group_count, - (count(DISTINCT - CASE - WHEN ((s.id IS NOT NULL) AND (s.deleted_at IS NULL)) THEN s.id - ELSE NULL::integer - END))::integer AS student_count, - o.country, - o.updated_at, - o.created_at, - o.deleted_at - FROM (((public.organizations o - LEFT JOIN public.chapters c ON ((c.organization_id = o.id))) - LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) - LEFT JOIN public.students s ON ((s.organization_id = o.id))) - GROUP BY o.id; - - -- -- Name: performance_per_group_per_skill_per_lessons _RETURN; Type: RULE; Schema: public; Owner: - -- @@ -1865,22 +1750,26 @@ CREATE OR REPLACE VIEW public.performance_per_group_per_skill_per_lessons AS -- --- Name: student_analytics_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- Name: subject_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- -CREATE OR REPLACE VIEW public.student_analytics_summaries AS - SELECT s.id, - s.organization_id, - s.first_name, - s.last_name, - s.old_group_id, - COALESCE(array_agg(en.group_id) FILTER (WHERE (en.group_id IS NOT NULL)), '{}'::bigint[]) AS enrolled_group_ids - FROM ((public.students s - JOIN public.organizations o ON ((s.organization_id = o.id))) - JOIN public.enrollments en ON ((s.id = en.student_id))) - WHERE (s.deleted_at IS NULL) - GROUP BY s.id, s.first_name, s.last_name - ORDER BY s.last_name, s.first_name; +CREATE OR REPLACE VIEW public.subject_summaries AS + SELECT su.id, + su.subject_name, + su.organization_id, + sum( + CASE + WHEN (a.deleted_at IS NOT NULL) THEN 0 + ELSE 1 + END) AS skill_count, + su.created_at, + su.updated_at, + su.deleted_at + FROM ((public.subjects su + LEFT JOIN public.assignments a ON ((su.id = a.subject_id))) + LEFT JOIN public.skills sk ON ((sk.id = a.skill_id))) + WHERE (sk.deleted_at IS NULL) + GROUP BY su.id; -- @@ -1904,32 +1793,30 @@ CREATE OR REPLACE VIEW public.student_averages AS JOIN public.lessons l ON (((l.id = g.lesson_id) AND (l.subject_id = su.id)))) GROUP BY s.id, su.id, sk.skill_name; - -- --- Name: student_lesson_details _RETURN; Type: RULE; Schema: public; Owner: - +-- Name: group_lesson_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- -CREATE OR REPLACE VIEW public.student_lesson_details AS - SELECT s.id AS student_id, - s.first_name, - s.last_name, - s.deleted_at AS student_deleted_at, - l.id AS lesson_id, - l.date, - l.deleted_at AS lesson_deleted_at, - l.subject_id, - round(avg(g.mark), 2) AS average_mark, - count(g.mark) AS grade_count, - COALESCE(jsonb_object_agg(g.skill_id, jsonb_build_object('mark', g.mark, 'grade_descriptor_id', g.grade_descriptor_id, 'skill_name', sk.skill_name)) FILTER (WHERE (sk.skill_name IS NOT NULL)), '{}'::jsonb) AS skill_marks - FROM ((((public.students s - JOIN public.enrollments en ON ((s.id = en.student_id))) - JOIN public.lessons l ON ((en.group_id = l.group_id))) - LEFT JOIN public.grades g ON (((g.student_id = s.id) AND (g.lesson_id = l.id) AND (g.deleted_at IS NULL)))) - LEFT JOIN public.skills sk ON ((sk.id = g.skill_id))) - WHERE ((en.active_since <= l.date) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= l.date))) - GROUP BY s.id, l.id - ORDER BY l.subject_id; - +CREATE OR REPLACE VIEW public.group_lesson_summaries AS + SELECT slu.lesson_id, + slu.lesson_date, + gr.id AS group_id, + gr.chapter_id, + slu.subject_id, + concat(gr.group_name, ' - ', c.chapter_name) AS group_chapter_name, + (round(avg(slu.average_mark), 2))::double precision AS average_mark, + (sum(slu.grade_count))::bigint AS grade_count, + (round((((sum( + CASE + WHEN (slu.grade_count = 0) THEN 0 + ELSE 1 + END))::numeric / (count(slu.*))::numeric) * (100)::numeric), 2))::double precision AS attendance + FROM ((public.student_lesson_summaries slu + JOIN public.groups gr ON ((slu.group_id = gr.id))) + JOIN public.chapters c ON ((gr.chapter_id = c.id))) + WHERE (slu.deleted_at IS NULL) + GROUP BY slu.lesson_id, gr.id, c.id, slu.subject_id, slu.lesson_date + ORDER BY slu.lesson_date; -- -- Name: student_lesson_summaries _RETURN; Type: RULE; Schema: public; Owner: - @@ -1986,26 +1873,146 @@ CREATE OR REPLACE VIEW public.student_tag_table_rows AS -- --- Name: subject_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- Name: group_summaries _RETURN; Type: RULE; Schema: public; Owner: - -- -CREATE OR REPLACE VIEW public.subject_summaries AS - SELECT su.id, - su.subject_name, - su.organization_id, +CREATE OR REPLACE VIEW public.group_summaries AS + SELECT g.id, + g.group_name, + g.deleted_at, + g.created_at, + g.chapter_id, + c.chapter_name, + o.id AS organization_id, + o.mlid AS organization_mlid, + c.mlid AS chapter_mlid, + g.mlid, + concat(o.mlid, '-', c.mlid, '-', g.mlid) AS full_mlid, + o.organization_name, sum( CASE - WHEN (a.deleted_at IS NOT NULL) THEN 0 - ELSE 1 - END) AS skill_count, - su.created_at, - su.updated_at, - su.deleted_at - FROM ((public.subjects su - LEFT JOIN public.assignments a ON ((su.id = a.subject_id))) - LEFT JOIN public.skills sk ON ((sk.id = a.skill_id))) - WHERE (sk.deleted_at IS NULL) - GROUP BY su.id; + WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN 1 + ELSE 0 + END) AS student_count + FROM ((((public.groups g + LEFT JOIN public.enrollments en ON ((g.id = en.group_id))) + LEFT JOIN public.students s ON ((s.id = en.student_id))) + LEFT JOIN public.chapters c ON ((g.chapter_id = c.id))) + LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) + GROUP BY g.id, c.id, o.id; + + +-- +-- Name: student_lesson_details _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.student_lesson_details AS + SELECT s.id AS student_id, + s.first_name, + s.last_name, + s.deleted_at AS student_deleted_at, + l.id AS lesson_id, + l.date, + l.deleted_at AS lesson_deleted_at, + l.subject_id, + round(avg(g.mark), 2) AS average_mark, + count(g.mark) AS grade_count, + COALESCE(jsonb_object_agg(g.skill_id, jsonb_build_object('mark', g.mark, 'grade_descriptor_id', g.grade_descriptor_id, 'skill_name', sk.skill_name)) FILTER (WHERE (sk.skill_name IS NOT NULL)), '{}'::jsonb) AS skill_marks + FROM ((((public.students s + JOIN public.enrollments en ON ((s.id = en.student_id))) + JOIN public.lessons l ON ((en.group_id = l.group_id))) + LEFT JOIN public.grades g ON (((g.student_id = s.id) AND (g.lesson_id = l.id) AND (g.deleted_at IS NULL)))) + LEFT JOIN public.skills sk ON ((sk.id = g.skill_id))) + WHERE ((en.active_since <= l.date) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= l.date))) + GROUP BY s.id, l.id + ORDER BY l.subject_id; + + +-- +-- Name: student_analytics_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.student_analytics_summaries AS + SELECT s.id, + s.organization_id, + s.first_name, + s.last_name, + s.old_group_id, + COALESCE(array_agg(en.group_id) FILTER (WHERE (en.group_id IS NOT NULL)), '{}'::bigint[]) AS enrolled_group_ids + FROM ((public.students s + JOIN public.organizations o ON ((s.organization_id = o.id))) + JOIN public.enrollments en ON ((s.id = en.student_id))) + WHERE (s.deleted_at IS NULL) + GROUP BY s.id, s.first_name, s.last_name + ORDER BY s.last_name, s.first_name; + + +-- +-- Name: chapter_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.chapter_summaries AS + SELECT c.id, + c.chapter_name, + c.mlid AS chapter_mlid, + o.mlid AS organization_mlid, + concat(o.mlid, '-', c.mlid) AS full_mlid, + c.organization_id, + o.organization_name, + c.deleted_at, + (count(DISTINCT + CASE + WHEN (g.deleted_at IS NULL) THEN g.id + ELSE NULL::integer + END))::integer AS group_count, + (count(DISTINCT + CASE + WHEN ((en.active_since <= CURRENT_DATE) AND ((en.inactive_since IS NULL) OR (en.inactive_since >= CURRENT_DATE)) AND (s.deleted_at IS NULL)) THEN s.id + ELSE NULL::integer + END))::integer AS student_count, + c.created_at, + c.updated_at + FROM ((((public.chapters c + LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) + LEFT JOIN public.enrollments en ON ((en.group_id = g.id))) + LEFT JOIN public.students s ON ((s.id = en.student_id))) + LEFT JOIN public.organizations o ON ((c.organization_id = o.id))) + GROUP BY c.id, o.id; + +-- +-- Name: organization_summaries _RETURN; Type: RULE; Schema: public; Owner: - +-- + +CREATE OR REPLACE VIEW public.organization_summaries AS + SELECT o.id, + o.organization_name, + o.mlid AS organization_mlid, + (count(DISTINCT + CASE + WHEN ((c.id IS NOT NULL) AND (c.deleted_at IS NULL)) THEN c.id + ELSE NULL::integer + END))::integer AS chapter_count, + (count(DISTINCT + CASE + WHEN ((g.id IS NOT NULL) AND (g.deleted_at IS NULL)) THEN g.id + ELSE NULL::integer + END))::integer AS group_count, + (count(DISTINCT + CASE + WHEN ((c.id IS NOT NULL) AND (c.deleted_at IS NULL)) THEN c.student_count + ELSE 0 + WHEN ((s.id IS NOT NULL) AND (s.deleted_at IS NULL)) THEN s.id + ELSE NULL::integer + END))::integer AS student_count, + o.country, + o.updated_at, + o.created_at, + o.deleted_at + FROM (((public.organizations o + LEFT JOIN public.chapters c ON ((c.organization_id = o.id))) + LEFT JOIN public.groups g ON ((g.chapter_id = c.id))) + LEFT JOIN public.students s ON ((s.organization_id = o.id))) + GROUP BY o.id; -- diff --git a/spec/components/table_components/table_spec.rb b/spec/components/table_components/table_spec.rb index 2f52d85c0..7e3d642c3 100644 --- a/spec/components/table_components/table_spec.rb +++ b/spec/components/table_components/table_spec.rb @@ -20,16 +20,23 @@ def initialize(item:, **_) let(:row_component) { StubTableRow } context 'when rows is empty' do + it 'renders the default empty message when no empty_message option is provided' do + render_inline(described_class.new(rows: [], row_component: row_component)) + + expect(page).to have_text(I18n.t(:no_results_found)) + end + it 'renders the empty message when the empty_message option is provided' do render_inline(described_class.new(rows: [], row_component: row_component, options: { empty_message: 'No items found' })) expect(page).to have_text('No items found') end - it 'does not render an empty message when no empty_message option is provided' do - render_inline(described_class.new(rows: [], row_component: row_component)) + it 'does not render an empty message when empty_message is false' do + render_inline(described_class.new(rows: [], row_component: row_component, options: { empty_message: false })) - expect(page).not_to have_css('[style*="grid-column: 1/-1"]') + expect(page).not_to have_text(I18n.t(:no_results_found)) + expect(page).not_to have_text('No items found') end end From 0a6d2dd77a2246994e2e604e89efa53ab5fb9e28 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Tue, 7 Jul 2026 12:54:52 +0200 Subject: [PATCH 6/8] feat(2602): remove changes in .gitignore --- .gitignore | 2 +- config/database.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 28617fb93..71ec0f9d1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,4 @@ yarn-debug.log* .yarn-integrity /app/assets/builds/* -!/app/assets/builds/.keep \ No newline at end of file +!/app/assets/builds/.keep diff --git a/config/database.yml b/config/database.yml index d505a1da9..a253baef9 100644 --- a/config/database.yml +++ b/config/database.yml @@ -5,7 +5,7 @@ default: &default host: <%= ENV['DATABASE_HOST'] || 'localhost' %> username: <%= ENV['DATABASE_USER'] || 'tracker' %> password: <%= ENV['DATABASE_PASSWORD'] || 'tracker' %> - port: 5433 + development: <<: *default database: tracker_development From 098d130335e6e54898de6f3813c730a6a4318935 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Tue, 7 Jul 2026 16:35:42 +0200 Subject: [PATCH 7/8] fix organization view rendering table --- app/views/organizations/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb index bd5de017a..94f9b8fbc 100644 --- a/app/views/organizations/index.html.erb +++ b/app/views/organizations/index.html.erb @@ -8,7 +8,7 @@
-<%= render TableComponents::Table.new(pagy: @pagy, rows: @organizations, row_component: TableComponents::OrganizationRow, : { empty_message: t(:no_organizations_found) }) do |t| +<%= render TableComponents::Table.new(pagy: @pagy, rows: @organizations, row_component: TableComponents::OrganizationRow, options: { empty_message: t(:no_organizations_found) }) do |t| t.with_left do %>
From 1ab608e2331f293e2782bdfedb80b0b07aabafc0 Mon Sep 17 00:00:00 2001 From: Kevin Rukundo Date: Thu, 9 Jul 2026 18:16:16 +0200 Subject: [PATCH 8/8] feat(2602): remove quotes on translations and add changes to CHANGELOG.MD file --- CHANGELOG.md | 81 +++++++++++++++++++++++++++++++++++++------ config/locales/en.yml | 8 ++--- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26732a5c7..74c159d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## Unreleased + +- Added a message to search results when no results are found - Added ability for multiple student selection in subject analytics - Fixed scoping issue in analytics - Handled duplicate lesson uploads by returning a `409` Conflict @@ -7,13 +9,15 @@ - Fixed the missing download button for Group Performance Graph ## 0.41.0 + - Added ability to edit skill and grade descriptions - Added ability to generate student-level reports - Defaulted starting date for analytics to the last lesson's month -- Replaced existing charts using the `Chart.js` library +- Replaced existing charts using the `Chart.js` library - Fixed issue where group reports crashed when students had no summaries ## 0.40.0 + - Added ability to filter analytics data by date - Added ability to select multiple groups in analytics - Updated database engine versions in pulumi @@ -23,18 +27,21 @@ - Fixed student lesson summaries showing empty on report screen ## 0.39.0 + - Extended importing students to handle all student fields - Added alert when viewing a group with students graded outside their enrollment -- Improved speed when querying students which have no active enrollments +- Improved speed when querying students which have no active enrollments - Removed 'Created' and added 'Enrolled Since' column to student table in group view - Added ability to update a lesson's date - Added ability to filter by single group in 'Group analytics' - Fixed Organization and Chapter summaries showing incorrect counts ## 0.38.1 - Fix Student Analytics Summaries -- Fixed Student Analytics Summaries causing crash when viewing Analytics + +- Fixed Student Analytics Summaries causing crash when viewing Analytics ## 0.38.0 - Student Imports & Enrollments + - Added dropdown to select enrollment starting date for new students in a group - Modified CI workflow with caching and cleanup separation, added retries using `rspec-retry` gem - Added ability to enroll students with no active enrollments in a group @@ -42,15 +49,17 @@ - Added organization statistics in separate component when viewing an organization ## 0.37.0 - Student Enrollments Overhaul + - Changed serializing and scoping for students to go through organization and by groups found in their enrollments - Modified generation of new MLIDs to go through `organization_id` for students - Overhauled student logic and related views to go exclusively through enrollments -- Dropped DB trigger to update enrollments on group change and added constraint for non-overlapping enrollments +- Dropped DB trigger to update enrollments on group change and added constraint for non-overlapping enrollments - Renamed `group_id` column to `old_group_id` for students - Filtered out deleted data when viewing group analytics - Upgraded Tailwind from V3 to V4 -## 0.36.0 - Reworked Student MLIDs and Tags +## 0.36.0 - Reworked Student MLIDs and Tags + - Added the ability to delete Tags - Added ability to search students by both organization and tag name - Updated subject analytics performance charts to render each grade score @@ -62,6 +71,7 @@ - Added autogeneration of student MLIDs when creating students ## 0.35.0 - Enrollment Date Fixes + - Enabled teachers to create and edit groups/students - Added toggle buttons in group reports - Added averages for each skill when viewing students @@ -76,6 +86,7 @@ - Added Datepicker component and modified forms with dates to use it ## 0.34.0 - Group Reports + - Added Count of Graded/absent/total students and total lesson average mark in lesson view - Changed Group lesson summaries to show average of students' averages - Modified enrollments trigger when updating students to properly update active_since and inactive_since fields @@ -93,6 +104,7 @@ - Fixed missing data for groups with only one lesson in group analytics ## 0.33.0 - Lesson UUIDs & Graphs + - Added validation for existing tags in an organization - Blocked users from removing a graded skill from a subject - Removed absence from student lesson summaries and added lesson date @@ -117,11 +129,13 @@ - Sorted organizations in new group form ## 0.31.1 - Ruby 3.3.0 + - Upgraded Ruby to 3.3.0 - Fixed #1805 - made sure to display form validation errors in red - Fixed #1804 - defaulting student DOB to January 1st ## 0.31.0 - Redesign + - Redesigned the whole application - Implemented Organization editing - Student creation will redirect to the previous location @@ -132,39 +146,48 @@ - Implemented smaller forms such as student tags, as animated drop forms on the index pages ## 0.30.2 - Rails 7 + - Updated Rails to v7 - Removed healthcheck from skylight endpoints ## 0.30.1 - Fix student table ordering + - Fixed the student table bug where table row number started at 2 ## 0.30.0 - Homepage + - Removed Sass - Implemented a marketing landing page - Fixed crash when navigating to a student tag that is in use by some students - Upgraded Ruby to 3.0.3 ## 0.29.1 - Structured Group Select -- Implemented structured group select in the student form + +- Implemented structured group select in the student form ## 0.29.0 - Changing Groups + - Enabled students to change groups - Created Enrollments to track groups the student was enrolled to over time ## 0.28.0 - Datapoints + - Upgraded to Ruby 3 - Upgraded Omniauth to v2 - Analytics filters are now stored in the URL making them shareable - Added Number of Datapoints to General Analytics ## 0.27.1 - Group/Student MLID fix + - Fixed Student MLID scoping by group instead of by chapter - Alphabetized Chapters in New Group form ## 0.27.0 - Group MLIDs -- Added MLID to Groups + +- Added MLID to Groups ## 0.26.0 - Organization and Chapter MLIDs + - Added subjects the skill belongs to in single skill view - Added MLIDs to Organizations - Added MLIDs to Chapters @@ -173,11 +196,13 @@ - Upgraded to Rails 6.1.2 ## 0.25.1. - Analytics UX Improvements + - Fixed Analytics dropdown defaulting to All Organizations - Fixed Analytics Subjects not displaying individual organizations - Alphabetized all Analytics dropdowns ## 0.25.0 - Optimizations, Deployment, Analytics + - Deployment for AWS - Optimized a few eager database queries - Fixed clicking Update Role when no role is selected crashing the app on the single user screen @@ -192,21 +217,25 @@ - Remove deleted resources from analytics ## 0.24.0 - Student Tags + - Upgraded Rails to 6.0.3.2 - Upgraded Ruby to 2.7.1 - Implement Student Tags - Use `c.` notation for estimated date ## 0.23.1 - Fix analytics display of individual students + - Fixed displaying group analytics instead of a single student when a student is selected ## 0.23.0 - Country of Nationality for Students + - Upgraded Ruby to 2.6.5 - Hiding creation buttons from users that aren't permitted to use them - Fixed issue with new MDL fields not being activated in coccoon forms - Added Country of Nationality field to Students ## 0.22.0 - Lesson Table Improvements + - Not displaying deleted skills when creating subject - Implemented student ordering in single lesson view - Default Lessons view order @@ -217,14 +246,17 @@ - Fix some lessons erroring out because of wrong date format ## 0.21.3 - Logs + - Updated Logging aggregation ## 0.21.2 - Page Titles + - Made snackbar more consistent by removing the in-out animation - Added autofocus to first field of most forms (student, group, chapter, ...) - Added page titles ## 0.21.1 - Student & Group Creation Improvements + - Adjusted layout for smaller screens in single lesson view - Fixed showing Student Lesson for a lesson when student is no longer in the group lesson was in - Added total average mark and grade count in single lesson view @@ -237,6 +269,7 @@ - Made "Create another" group prompt pre-fill the current chapter ## 0.21.0 - Integrated Analytics + - Redesigned Lesson Page - Added nearby lesson performance visualization to the Lesson Page - Redesigned Skill Page @@ -247,27 +280,32 @@ - Added clicking on data points in Group Analytics to go to individual lessons ## 0.20.0 - Sidebar image and Mobile app link + - Limit user image size to 50px in the sidebar - Added link to download mobile app ## 0.19.0 - Deletion Policy and navigation improvement + - Improved the performance of student index listing (fix for N+1 query) - Fixed inconsistencies in UI after clicking back/forward - MDL and Turbolinks were not properly resetting - Upgraded Ruby to 2.6.4 - Only allow global administrators with a higher global role to delete users ## 0.18.0 - User Deletion and Login Improvements + - Implemented User Deletion - Upgrade Ruby to 2.6.3 - Fix Google Login for users that have uppercase letters in their emails - Differentiate between invalid Google id_token and a valid id_token but non-existent MindLeaps user ## 0.17.0 - Policy scoped Grades API + - Policy scoped grade v2 API - Fix N+1 Query on individual user view - Implemented search for Skills ## 0.16.0 - Search + - Updated table styles to simplify and unify table margins - Implemented Search for Students table - Implemented Search for Groups table @@ -275,11 +313,13 @@ - Handle pagination overflows by displaying the last page ## 0.15.1 + - Updated views for new grade structure - Policy scoping API index responses for V2 of API - Fix v2 deletion endpoint to properly use lesson UUID instead of the old ID ## 0.15.0 + - Added authorization for v2 API - Added a v2 API for grades that uses skill_id and mark to relate to grade_descriptors - Added skill_id and mark to Grades @@ -290,52 +330,63 @@ - Added paging to first and last pages ## 0.14.1 + - Add Material loading bar for Turbolinks - Fix Highcharts instantiating multiple times because of Turbolinks ## 0.14.0 + - Disable Performance tab for students that have no grades - Improve UX of back/up button in student pages - Integrated Turbolinks to improve app performance ## 0.13.0 + - Require students to belong to a group - Remove organization association from students ## 0.12.1 + - Show Restore deleted group button on group show - Redirect after group deletion back to referrer ## 0.12.0 + - Show Deleted functionality for Students and Groups - Undelete Students and Groups - Student header actions now available on the student images view ## 0.11.1 + - Added request_id key for logging - Added meta-request for dev information in Chrome dev tools - Fixed N+1 query with loading current_user roles ## 0.11.0 + - Adds Edit Student button to each row in the student table - Back button now returns back to the referrer page (like browser back) - Submitting grades for student now redirects back to the lesson page ## 0.10.0 + - Enable users to generate a short lived API token on their profile page - Preserving Student gender when editing student details - Exclude empty student lessons, by default, in Student performance view - Incorporated student images in the student menu, along with details and performance ## 0.9.0 -- GET /grades API returns only grades that were updated within the last 4 months + +- GET /grades API returns only grades that were updated within the last 4 months - Separated student performance and details into separate tabs - Displaying student performance on student page ## 0.8.1 + - Fix Scoping for GroupSummary ## 0.8.0 + - Caching drawer navigation based on request path - Using "Unknown" instead of "Unactive User" - Improved Lesson View with average mark and graded skills @@ -343,9 +394,11 @@ - Counting only undeleted students in group and chapter student count ## 0.7.1 + - Paging in resource tables ## 0.7.0 + - POSTing to /grades to create a new grade will update the existing grade and return 200 instead of returning 409 conflict - Upgraded Rails from 5.2.0 to 5.2.1 - Upgraded pg from 1.0.0 to 1.1.2 @@ -353,12 +406,14 @@ - Upgraded Pundit from 1.1.0 to 2.0.0 ## 0.6.0 + - Upgraded Skylight from 1.6.1 to 2.0.1 - Upgraded Fog Core from 1.45.0 to 2.1.0 - Upgraded Fog AWS from 2.0.1 to 3.0.0 - Removed sdoc ## 0.5.0 + - Setup log format to JSON - Sending logs to Datadog - Upgraded pg from 0.21.0 to 1.0.0 @@ -367,34 +422,40 @@ - Upgraded Skylight from 1.6.0 to 1.6.1 ## 0.4.2 + - Upgraded Tiddle from 1.1.0 to 1.2.0 - Upgraded Rails from 5.1.5 to 5.1.6 - Upgraded New Relic agent from 4.8 to 5.0 - Upgraded Skylight from 1.5.1 to 1.6.0 ## 0.4.1 + - Fixed Datadog DB connect address ## 0.4.0 + - Upgraded Rails from 5.1.4 to 5.1.5 -- Upgraded Puma from 3.11.2 to 3.11.3 +- Upgraded Puma from 3.11.2 to 3.11.3 - Upgraded Simple Form from 3.5.0 to 3.5.1 - Upgraded Bullet from 5.7.2 to 5.7.5 - Upgraded Selenium-webdriver from 3.9.0 to 3.11.0 - Upgraded Rubocop from 0.52.1 to 0.53.0 - Upgraded Devise from 4.3.0 to 4.4.3 - Upgraded Tiddle from 1.0.2 to 1.1.0 -- Integrated with Datadog (base, nginx, and Postgres) +- Integrated with Datadog (base, nginx, and Postgres) ## 0.3.0 + - Revert Policy scoping on Organizations API (The Mobile is not ready) - Revert removing `after_timestamp` parameter from Organizations API ## ~~0.2.0~~ + - Upgraded capybara from 2.17.0 to 2.18.0 - Upgraded i18n from 0.9.4 to 0.9.5 - ~~Applying Policy scope to Organizations API~~ - ~~ignoring after_timestamp parameter for Organizations API~~ ## 0.1.0 + - Base Release diff --git a/config/locales/en.yml b/config/locales/en.yml index a9060b8d1..a6f30d89b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -357,10 +357,10 @@ en: health_insurance: Health Insurance health_issues: Health Issues hiv_tested: HIV Tested - no_students_found: "No Students found" - no_groups_found: "No Groups found" - no_chapters_found: "No Chapters found" - no_organizations_found: "No Organizations found" + no_students_found: No Students found + no_groups_found: No Groups found + no_chapters_found: No Chapters found + no_organizations_found: No Organizations found no_results_found: No results found confirm: Confirm