From 317209303332fc722a20c9a83b33c1b634983535 Mon Sep 17 00:00:00 2001 From: Formasitchijoh Date: Thu, 19 Dec 2024 21:18:29 +0100 Subject: [PATCH 01/16] fix:increase Capybara default wait time to resolve category_scopes_specs failures --- spec/rails_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index c54f21ab90..a89ea159a5 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -26,7 +26,7 @@ Capybara::Screenshot.prune_strategy = :keep_last_run Capybara.save_path = 'tmp/screenshots/' Capybara.server = :puma, { Silent: true } - +Capybara.default_max_wait_time = 10 # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end From da90e459ba663846d182b83c4a113da01dc48056 Mon Sep 17 00:00:00 2001 From: Formasitchijoh Date: Thu, 19 Dec 2024 21:21:06 +0100 Subject: [PATCH 02/16] fix: use CampaignList index to ensure unique keys for course campaigns --- app/assets/javascripts/components/overview/campaign_list.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/components/overview/campaign_list.jsx b/app/assets/javascripts/components/overview/campaign_list.jsx index bf6baaecce..ad05b32e24 100644 --- a/app/assets/javascripts/components/overview/campaign_list.jsx +++ b/app/assets/javascripts/components/overview/campaign_list.jsx @@ -11,7 +11,7 @@ const CampaignList = ({ campaigns, course }) => { let comma = ''; const url = `/campaigns/${campaign.slug}`; if (index !== lastIndex) { comma = ', '; } - return {campaign.title}{comma}; + return {campaign.title}{comma}; }) : I18n.t('courses.none')); From e43a8f240d5426940ba102c26d3be8c36f1ab702 Mon Sep 17 00:00:00 2001 From: Formasitchijoh Date: Thu, 19 Dec 2024 22:40:50 +0100 Subject: [PATCH 03/16] fix: handle undefined 'valid' and 'search' values in course_reator page --- .../components/course_creator/course_creator.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/components/course_creator/course_creator.jsx b/app/assets/javascripts/components/course_creator/course_creator.jsx index 1b7593ea37..fda1d2c5d9 100644 --- a/app/assets/javascripts/components/course_creator/course_creator.jsx +++ b/app/assets/javascripts/components/course_creator/course_creator.jsx @@ -114,7 +114,7 @@ const CourseCreator = createReactClass({ campaignParam() { // The regex allows for any number of URL parameters, while only capturing the campaign_slug parameter - const campaignParam = window.location.search.match(/\?.*?campaign_slug=(.*?)(?:$|&)/); + const campaignParam = window.location?.search?.match(/\?.*?campaign_slug=(.*?)(?:$|&)/); if (campaignParam) { return campaignParam[1]; } @@ -162,8 +162,11 @@ const CourseCreator = createReactClass({ cleanedCourse.scoping_methods = getScopingMethods(this.props.scopingMethods); this.props.submitCourse({ course: cleanedCourse }, onSaveFailure); } - } else if (!this.props.validations.exists.valid) { - this.setState({ isSubmitting: false }); + } else { + const existsValidation = this.props.validations?.exists?.valid; + if (existsValidation === false) { + this.setState({ isSubmitting: false }); + } } }, From a7eaabcada31c1d3783a7a7c647578da133c7d58 Mon Sep 17 00:00:00 2001 From: Abishek Das Date: Wed, 11 Dec 2024 15:14:30 +0530 Subject: [PATCH 04/16] feat: enhance WeekdayPicker accessibility with aria-label and aria-live support - Add `aria-label` and `aria-live` for improved screen reader compatibility. - Add CSS styling for the `aria-live` region to ensure proper visibility and user experience. - remove aria-pressed --- .../components/common/weekday_picker.jsx | 22 ++++++++++++++----- app/assets/stylesheets/modules/_calendar.styl | 12 ++++++++++ config/locales/en.yml | 11 ++++++++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/components/common/weekday_picker.jsx b/app/assets/javascripts/components/common/weekday_picker.jsx index 56f352e667..93e48f4dd5 100644 --- a/app/assets/javascripts/components/common/weekday_picker.jsx +++ b/app/assets/javascripts/components/common/weekday_picker.jsx @@ -25,7 +25,6 @@ const WeekdayPicker = ({ style, tabIndex, - ariaModifier, modifiers, locale, @@ -145,7 +144,7 @@ const WeekdayPicker = ({ dayClassName += customModifiers.map(modifier => ` ${dayClassName}--${modifier}`).join(''); - const ariaSelected = customModifiers.indexOf(ariaModifier) > -1; + const ariaSelected = customModifiers.indexOf('selected') > -1; let tabIndexValue = null; if (onWeekdayClick) { @@ -159,10 +158,20 @@ const WeekdayPicker = ({ const onMouseEnterHandler = onWeekdayMouseEnter ? e => handleWeekdayMouseEnter(e, weekday, customModifiers) : null; const onMouseLeaveHandler = onWeekdayMouseLeave ? e => handleWeekdayMouseLeave(e, weekday, customModifiers) : null; + const ariaLabelMessage = ariaSelected + ? I18n.t('weekday_picker.aria.weekday_selected', { weekday: localeUtils.formatWeekdayLong(weekday), }) + : I18n.t('weekday_picker.aria.weekday_select', { weekday: localeUtils.formatWeekdayLong(weekday), }); + + const ariaLiveMessage = ariaSelected + ? I18n.t('weekday_picker.aria.weekday_selected', { weekday: localeUtils.formatWeekdayLong(weekday), }) + : I18n.t('weekday_picker.aria.weekday_unselected', { weekday: localeUtils.formatWeekdayLong(weekday), }); + return ( ); }; @@ -201,7 +214,6 @@ WeekdayPicker.propTypes = { style: PropTypes.object, tabIndex: PropTypes.number, - ariaModifier: PropTypes.string, modifiers: PropTypes.object, locale: PropTypes.string, diff --git a/app/assets/stylesheets/modules/_calendar.styl b/app/assets/stylesheets/modules/_calendar.styl index 1182b48b4f..67e1711531 100644 --- a/app/assets/stylesheets/modules/_calendar.styl +++ b/app/assets/stylesheets/modules/_calendar.styl @@ -172,3 +172,15 @@ .DayPicker--ar direction rtl + +// CSS for screen reader support for weekday picker +.sr-WeekdayPicker-aria-live { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 590544a53c..ba7dd1307b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1870,9 +1870,16 @@ en: note_edit_cancel_button_focused: "Cancel note edit {{title}}" note_delete_button_focused: "Delete the note {{title}}" note_edit_mode: "Entered Edit mode for note titled: {{title}}. You can now edit the title and content." - + customize_error_message: JSONP_request_failed: "The request to fetch data from Wikipedia has timed out. This may be due to a slow internet connection or temporary server issues. Please try again later." tagged_courses: - download_csv: 'Download CSV' \ No newline at end of file + download_csv: 'Download CSV' + + weekday_picker: + aria: + weekday_select: "{{weekday}} Press Return key to select" + weekday_selected: "{{weekday}} Selected Press Return Key to unselect" + weekday_unselected: "{{weekday}} Unselected" + \ No newline at end of file From b2b9aa1d68f58e64ab4d46be8b129ce3f4c8c518 Mon Sep 17 00:00:00 2001 From: Amine Hassou Date: Thu, 26 Dec 2024 03:52:53 +0100 Subject: [PATCH 05/16] Initial work on adding new student endpoint --- app/controllers/courses_controller.rb | 6 ++++++ app/models/course.rb | 8 ++++++++ config/routes.rb | 4 +++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 436557d183..0b67f73dc0 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -139,6 +139,12 @@ def alerts @alerts = current_user&.admin? ? @course.alerts : @course.public_alerts end + def approved_classroom_courses_json + courses = Course.approved_classroom_courses_with_users + + render json: courses, include: :users + end + ########################## # User-initiated actions # ########################## diff --git a/app/models/course.rb b/app/models/course.rb index 80e1fb2c35..f7bf7ce011 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -160,6 +160,14 @@ def self.unsubmitted .where(submitted: false).references(:campaigns) end + def self.approved_classroom_courses_with_users + nonprivate + .where(type: 'ClassroomProgramCourse', withdrawn: false) + .joins(:campaigns) + .distinct + .includes(:users) + end + scope :strictly_current, -> { where('? BETWEEN start AND end', Time.zone.now) } scope :current, -> { current_and_future.where('start < ?', Time.zone.now) } scope :ended, -> { where('end < ?', Time.zone.now) } diff --git a/config/routes.rb b/config/routes.rb index 7b48dd80f2..fd1b378d25 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -168,7 +168,9 @@ titleterm: /[^\/]*/, _subsubsubpage: /.*/ } - + get '/courses/approved_classroom_courses.json', + to: 'courses#approved_classroom_courses_json', + as: :approved_classroom_courses post '/courses/:slug/students/add_to_watchlist', to: 'courses/watchlist#add_to_watchlist', as: 'add_to_watchlist', constraints: { slug: /.*/ } delete 'courses/:slug/delete_from_campaign' => 'courses/delete_from_campaign#delete_course_from_campaign', as: 'delete_from_campaign', From 23e4a782f5a66d3e1c27638459fec77caf9a083e Mon Sep 17 00:00:00 2001 From: Amine Hassou Date: Sun, 29 Dec 2024 12:12:29 +0100 Subject: [PATCH 06/16] Added endpoints for classroomprogram courses and fellowscohort courses --- app/controllers/courses_controller.rb | 24 ++++++++++++++++++++--- app/models/course.rb | 28 +++++++++++++++++++++++++-- config/routes.rb | 17 +++++++++++++--- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 0b67f73dc0..9dfb51e404 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -139,10 +139,28 @@ def alerts @alerts = current_user&.admin? ? @course.alerts : @course.public_alerts end - def approved_classroom_courses_json - courses = Course.approved_classroom_courses_with_users + def classroom_program_students_json + courses = Course.classroom_program_students - render json: courses, include: :users + render json: courses, include: :students + end + + def classroom_program_students_and_instructors_json + courses = Course.classroom_program_students_and_instructors + + render json: courses, include: [:students, :instructors] + end + + def fellows_cohort_students_json + courses = Course.fellows_cohort_students + + render json: courses, include: :students + end + + def fellows_cohort_students_and_instructors_json + courses = Course.fellows_cohort_students_and_instructors + + render json: courses, include: [:students, :instructors] end ########################## diff --git a/app/models/course.rb b/app/models/course.rb index f7bf7ce011..02e50ddfe4 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -160,12 +160,36 @@ def self.unsubmitted .where(submitted: false).references(:campaigns) end - def self.approved_classroom_courses_with_users + def self.classroom_program_students nonprivate .where(type: 'ClassroomProgramCourse', withdrawn: false) .joins(:campaigns) .distinct - .includes(:users) + .includes(:students) + end + + def self.classroom_program_students_and_instructors + nonprivate + .where(type: 'ClassroomProgramCourse', withdrawn: false) + .joins(:campaigns) + .distinct + .includes(:students, :instructors) + end + + def self.fellows_cohort_students + nonprivate + .where(type: 'FellowsCohort', withdrawn: false) + .joins(:campaigns) + .distinct + .includes(:students) + end + + def self.fellows_cohort_students_and_instructors + nonprivate + .where(type: 'FellowsCohort', withdrawn: false) + .joins(:campaigns) + .distinct + .includes(:students, :instructors) end scope :strictly_current, -> { where('? BETWEEN start AND end', Time.zone.now) } diff --git a/config/routes.rb b/config/routes.rb index fd1b378d25..af7c002c19 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -168,9 +168,20 @@ titleterm: /[^\/]*/, _subsubsubpage: /.*/ } - get '/courses/approved_classroom_courses.json', - to: 'courses#approved_classroom_courses_json', - as: :approved_classroom_courses + + get '/courses/classroom_program_students.json', + to: 'courses#classroom_program_students_json', + as: :classroom_program_students + get '/courses/classroom_program_students_and_instructors.json', + to: 'courses#classroom_program_students_and_instructors_json', + as: :classroom_program_students_and_instructors + get '/courses/fellows_cohort_students.json', + to: 'courses#fellows_cohort_students_json', + as: :fellows_cohort_students + get '/courses/fellows_cohort_students_and_instructors.json', + to: 'courses#fellows_cohort_students_and_instructors_json', + as: :fellows_cohort_students_and_instructors + post '/courses/:slug/students/add_to_watchlist', to: 'courses/watchlist#add_to_watchlist', as: 'add_to_watchlist', constraints: { slug: /.*/ } delete 'courses/:slug/delete_from_campaign' => 'courses/delete_from_campaign#delete_course_from_campaign', as: 'delete_from_campaign', From 41e0c8ea3a0c4c4d2fe762d228e6dbcc25f28a36 Mon Sep 17 00:00:00 2001 From: empty-codes Date: Mon, 30 Dec 2024 18:38:35 +0100 Subject: [PATCH 07/16] Adds Admin guide explaining how the dashboard is run on Toolforge and CloudVPS --- docs/admin_guide.md | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 docs/admin_guide.md diff --git a/docs/admin_guide.md b/docs/admin_guide.md new file mode 100644 index 0000000000..42879efb43 --- /dev/null +++ b/docs/admin_guide.md @@ -0,0 +1,97 @@ +# Admin Guide for Program & Events Dashboard + +This guide provides an overview of the Program & Events Dashboard. It also offers resources for managing and troubleshooting the system. + +## Table of Contents +1. [Overview](#overview) +2. [Monitoring and Logs](#monitoring-and-logs) + - [Toolforge](#toolforge) + - [Cloud VPS](#cloud-vps) +3. [Troubleshooting](#troubleshooting) + - [Web Server Issues](#web-server-issues) + - [Database Issues](#database-issues) + - [Data Dumps and Recovery](#data-dumps-and-recovery) +4. [More Resources](#more-resources) + +## Overview + +The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://outreachdashboard.wmflabs.org)) is a web application designed to support the global Wikimedia community in organizing various programs, including edit-a-thons, education initiatives, and other events. **[Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboard/tree/wmflabs)** **[Phabricator Project](https://phabricator.wikimedia.org/project/manage/1052/)** + +### Infrastructure Overview +- **Toolforge Tool**: [wikiedudashboard](https://toolsadmin.wikimedia.org/tools/id/wikiedudashboard) + - **Source code: [WikiEduDashboardTools](https://github.com/WikiEducationFoundation/WikiEduDashboardTools)**: A collection of PHP endpoints that retrieve revision and article data from Wikimedia Replica databases in the Wikimedia Cloud environment. + - **Deployed at**: [wikiedudashboard.toolforge.org](https://wikiedudashboard.toolforge.org/), [replica-revision-tools.wmcloud.org](https://replica-revision-tools.wmcloud.org/) + +- **Cloud VPS Project**: [globaleducation](https://openstack-browser.toolforge.org/project/globaleducation) + +### Servers + +1. **Web Server** + - **`peony-web.globaleducation.eqiad1.wikimedia.cloud`** + - Hosts the main web application and core Sidekiq processes using **RVM (Ruby Version Manager)**, **Phusion Passenger**, and **Apache**. + - **Capistrano** is used for deployments + - Sidekiq processes hosted: + - `sidekiq-default`: Handles transactional jobs (e.g., wiki edits, email notifications). + - `sidekiq-constant`: Manages frequently run tasks (e.g., adding courses to update queues). + - `sidekiq-daily`: Executes long-running daily update tasks. + +2. **Sidekiq Servers**: These dedicated servers handle the other Sidekiq processes to isolate bottlenecks and failures: + - **`peony-sidekiq.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-short` for short-running course updates. + - **`peony-sidekiq-medium.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-medium` for typical course updates. + - **`peony-sidekiq-3.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-long` for long-running course updates with higher queue latency. + +3. **Database Server** + - **`peony-database.globaleducation.eqiad1.wikimedia.cloud`**: Manages the Dashboard's primary database. + +4. **Redis Server** + - **`p-and-e-dashboard-redis.globaleducation.eqiad1.wikimedia.cloud`**: Shared across all Sidekiq processes for task queuing and caching. + + +## Monitoring and Logs + +#### Toolforge +- [Kubernetes Namespace Details](https://k8s-status.toolforge.org/namespaces/tool-wikiedudashboard/) +- [Kubernetes Pod Details](https://k8s-status.toolforge.org/namespaces/tool-wikiedudashboard/pods/wikiedudashboard-5954f86c86-pm8d5/) + + +#### Cloud VPS +- [Grafana](https://grafana.wmcloud.org/d/0g9N-7pVz/cloud-vps-project-board?orgId=1&var-project=globaleducation) +- [Server Admin Logs (SAL)](https://sal.toolforge.org/globaleducation) +- [Alerts](https://prometheus-alerts.wmcloud.org/?q=%40state%3Dactive&q=project%3Dglobaleducation) +- [Puppet agent logs for the globaleducation project](https://grafana.wmcloud.org/d/SQM7MJZSz/cloud-vps-puppet-agents?orgId=1&var-project=globaleducation&from=now-2d&to=now) + + +## Troubleshooting + +### Web Server Issues +- **Internal Server Error**: Restart the web server. +- **Unresponsive Web Service**: + - Usually caused by high-activity events or surges in ongoing activity, leading to system overload. + - **Solution**: Reboot the VM (instance) running the web server. + - The web service typically recovers within a few hours. + +### Database Issues +- **Full Disk**: Free up space by deleting temporary tables. +- **High-Edit / Long Courses Causing Errors**: + - Consider turning off the 'long' and 'very_long_update' queues. +- **Stuck Transactions**: If results in the Rails server becoming unresponsive, restart MySQL. +- **Database Errors**: + - Verify that the app and database server versions are compatible. + +### Data Dumps and Recovery +- **Performing a Dump for a table**: + 1. Put the database in `innodb_force_recovery=1` mode. + - Note: `OPTIMIZE TABLE revisions;` cannot run in recovery mode because the database is read-only. + 2. Start the dump process. + 3. Once the dump is complete, drop the table. + 4. Remove the database from recovery mode and restore the table. + +### Third-Party Dependencies +Issues could also be caused by maintenance or outages in third-party dependencies such as Openstack, Toolforge, or other services. + + +## More Resources +- [Toolforge Documentation](https://wikitech.wikimedia.org/wiki/Help:Toolforge) +- [Cloud VPS Documentation](https://wikitech.wikimedia.org/wiki/Help:Cloud_VPS) +- [Cloud VPS Admin Documentation](https://wikitech.wikimedia.org/wiki/Portal:Cloud_VPS/Admin) +- [Details of most recent P&E server update](https://github.com/WikiEducationFoundation/WikiEduDashboard/commit/df271f1c54fd0520e42445fcc88f19b6d03a603b#diff-f8eaa8feeef99c2b098e875ccdace93998b84eeb4110dc9f49b1327df7d96e21) From 6afed6ee9bbe9b83e884961c3b823016bedb0e6d Mon Sep 17 00:00:00 2001 From: Amine Hassou Date: Tue, 31 Dec 2024 17:13:53 +0100 Subject: [PATCH 08/16] Filtered out data from newly-added endpoints for easier access --- app/controllers/courses_controller.rb | 46 ++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 9dfb51e404..49cbcf7ad5 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -141,26 +141,56 @@ def alerts def classroom_program_students_json courses = Course.classroom_program_students - - render json: courses, include: :students + render json: courses.as_json( + only: %i[title created_at updated_at start end school term slug], + include: { + students: { + only: %i[username created_at updated_at permissions] + } + } + ) end def classroom_program_students_and_instructors_json courses = Course.classroom_program_students_and_instructors - - render json: courses, include: [:students, :instructors] + render json: courses.as_json( + only: %i[title created_at updated_at start end school term slug], + include: { + students: { + only: %i[username created_at updated_at permissions] + }, + instructors: { + only: %i[username created_at updated_at permissions] + } + } + ) end def fellows_cohort_students_json courses = Course.fellows_cohort_students - - render json: courses, include: :students + render json: courses.as_json( + only: %i[title created_at updated_at start end school term slug], + include: { + students: { + only: %i[username created_at updated_at permissions] + } + } + ) end def fellows_cohort_students_and_instructors_json courses = Course.fellows_cohort_students_and_instructors - - render json: courses, include: [:students, :instructors] + render json: courses.as_json( + only: %i[title created_at updated_at start end school term slug], + include: { + students: { + only: %i[username created_at updated_at permissions] + }, + instructors: { + only: %i[username created_at updated_at permissions] + } + } + ) end ########################## From cb553365e4d5ea30cd746bca0a0cee966865a715 Mon Sep 17 00:00:00 2001 From: Abishek Das Date: Tue, 31 Dec 2024 22:18:14 +0530 Subject: [PATCH 09/16] fix(DiffViewer & ArticleViewer): handle moved articles using MediaWiki page ID - Added "crossCheckArticleTitle" function to verify and update article titles and URLs using the MediaWiki API based on the page ID. - Introduced "UPDATE_ARTICLE_TITLE_AND_URL" action in the reducer to update article titles and URLs in the Redux store when article title is changed. - Updated "fetchArticleDetails" to integrate "crossCheckArticleTitle" for dynamic title validation and updates before if fetching article first and last revision fails. - Enhanced handling of moved articles by verifying and updating titles via MediaWiki API by using "crossCheckArticleTitle" in ArticleViewer. --- .../javascripts/actions/article_actions.js | 87 +++++++++++++++++-- .../containers/ArticleViewer.jsx | 30 +++++++ app/assets/javascripts/constants/articles.js | 1 + app/assets/javascripts/reducers/articles.js | 13 ++- app/views/courses/articles.json.jbuilder | 6 +- 5 files changed, 127 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/actions/article_actions.js b/app/assets/javascripts/actions/article_actions.js index 17243a6890..5f47610bff 100644 --- a/app/assets/javascripts/actions/article_actions.js +++ b/app/assets/javascripts/actions/article_actions.js @@ -1,27 +1,70 @@ import * as types from '../constants'; import API from '../utils/api.js'; import { getRevisionRange } from '../utils/mediawiki_revisions_utils'; +import { find } from 'lodash-es'; // This action uses the Thunk middleware pattern: instead of returning a plain -// action object, it returns a function that takes the store dispatch fucntion — +// action object, it returns a function that takes the store dispatch function — // which Thunk automatically provides — and can then dispatch a series of plain // actions to be handled by the store. // This is how actions with side effects — such as API calls — are handled in // Redux. export function fetchArticleDetails(articleId, courseId) { - return function (dispatch) { + return async function (dispatch, getState) { return API.fetchArticleDetails(articleId, courseId) .then((response) => { - // eslint-disable-next-line no-console const details = response.article_details; + return getRevisionRange(details.apiUrl, details.articleTitle, details.editors, details.startDate, details.endDate) - // eslint-disable-next-line no-console - .then(revisionRange => dispatch({ type: types.RECEIVE_ARTICLE_DETAILS, articleId, details, revisionRange })); + .then(async (revisionRange) => { + // If no revisions are found (both first and last revisions are missing), + // it may indicate a mismatch in the article title as the article was moved to a new title. + if (!revisionRange.first_revision && !revisionRange.last_revision) { + const { title: articleTitle, mw_page_id: article_mw_page_id } = find( + getState().articles.articles, + { id: articleId } + ); + + // Dispatch an action to cross-check the article title with its metadata. + const crossCheckedArticleTitle = await dispatch(crossCheckArticleTitle(articleId, articleTitle, article_mw_page_id)); + + // Re-fetch the article details using the cross-checked title for accuracy. + fetchArticleDetailsAgain(crossCheckedArticleTitle, articleId, courseId, dispatch); + } else { + dispatch({ type: types.RECEIVE_ARTICLE_DETAILS, articleId, details, revisionRange }); + } + }); }) .catch(response => (dispatch({ type: types.API_FAIL, data: response }))); }; } +// Re-fetches article details using the corrected or cross-checked article title. +// This function is used when the initial fetch fails to retrieve valid revision data, +// likely due to a mismatch in the article title. It ensures the Redux store is updated +// with accurate article details and revision ranges after the re-fetch. +function fetchArticleDetailsAgain(crossCheckedArticleTitle, articleId, courseId, dispatch) { + return API.fetchArticleDetails(articleId, courseId) + .then((response) => { + const details = response.article_details; + + // Calculate the revision range for the updated article title. + return getRevisionRange( + details.apiUrl, + crossCheckedArticleTitle, + details.editors, + details.startDate, + details.endDate + ).then((revisionRange) => { + // Dispatch the updated article details and revision range to Redux. + dispatch({ type: types.RECEIVE_ARTICLE_DETAILS, articleId, details, revisionRange }); + }); + }) + .catch((response) => { + (dispatch({ type: types.API_FAIL, data: response })); + }); +} + export function updateArticleTrackedStatus(articleId, courseId, tracked) { return function (dispatch) { return API.updateArticleTrackedStatus(articleId, courseId, tracked).then(response => (dispatch({ @@ -32,3 +75,37 @@ export function updateArticleTrackedStatus(articleId, courseId, tracked) { }))).catch(response => (dispatch({ type: types.API_FAIL, data: response }))); }; } + +export const crossCheckArticleTitle = (articleId, articleTitle, article_mw_page_id) => { + return async (dispatch) => { + try { + // Fetch the page title from Wikipedia API + const response = await fetch( + `https://en.wikipedia.org/w/api.php?action=query&pageids=${article_mw_page_id}&format=json&origin=*` + ); + + if (!response.ok) { + throw new Error(`API request failed with status ${response.status}`); + } + + const apiResponse = await response.json(); + const wikipediaArticleTitle = apiResponse.query.pages[article_mw_page_id]?.title; + + if (wikipediaArticleTitle && wikipediaArticleTitle !== articleTitle) { + const baseUrl = 'https://en.wikipedia.org/wiki/'; + const updatedUrl = `${baseUrl}${wikipediaArticleTitle.replace(/ /g, '_')}`; + + dispatch({ + type: types.UPDATE_ARTICLE_TITLE_AND_URL, + payload: { articleId, title: wikipediaArticleTitle, url: updatedUrl }, + }); + + return wikipediaArticleTitle; + } + + return articleTitle; + } catch (error) { + dispatch({ type: types.API_FAIL, data: error }); + } + }; +}; diff --git a/app/assets/javascripts/components/common/ArticleViewer/containers/ArticleViewer.jsx b/app/assets/javascripts/components/common/ArticleViewer/containers/ArticleViewer.jsx index 4e3cec2294..5720a8f1bf 100644 --- a/app/assets/javascripts/components/common/ArticleViewer/containers/ArticleViewer.jsx +++ b/app/assets/javascripts/components/common/ArticleViewer/containers/ArticleViewer.jsx @@ -27,6 +27,7 @@ import colors from '@components/common/ArticleViewer/constants/colors'; // Actions import { resetBadWorkAlert, submitBadWorkAlert } from '~/app/assets/javascripts/actions/alert_actions.js'; +import { crossCheckArticleTitle } from '@actions/article_actions'; /* Quick summary of the ArticleViewer component's main logic @@ -57,6 +58,10 @@ const ArticleViewer = ({ showOnMount, users, showArticleFinder, showButtonLabel, const [pendingRequest, setPendingRequest] = useState(false); const lastRevisionId = useSelector(state => state.articleDetails[article.id]?.last_revision?.revid); + // State to track whether the article title needs to be verified and updated + // (i.e., if a fetch failed due to the article title being moved) + const [checkArticleTitle, setCheckArticleTitle] = useState(false); + const dispatch = useDispatch(); const ref = useRef(); const isFirstRender = useRef(true); @@ -245,6 +250,8 @@ const ArticleViewer = ({ showOnMount, users, showArticleFinder, showButtonLabel, setFailureMessage(error.message); setFetched(true); setWhoColorFailed(true); + // Set flag to verify and fetch the article title if the fetch failed, possibly due to the article being moved + setCheckArticleTitle(true); }); }; @@ -257,9 +264,32 @@ const ArticleViewer = ({ showOnMount, users, showArticleFinder, showButtonLabel, }).catch((error) => { setWhoColorFailed(true); setFailureMessage(error.message); + // Set flag to verify and fetch the article title if the fetch failed, possibly due to the article being moved + setCheckArticleTitle(true); }); }; + // Function to verify if the article title has changed and fetch updated data accordingly + const verifyAndFetchArticle = async () => { + // Dispatch an action to cross-check the current article title using its ID and MediaWiki page ID + const crossCheckedArticleTitle = await dispatch(crossCheckArticleTitle(article.id, article.title, article.mw_page_id)); + + if (crossCheckedArticleTitle === article.title) { + setWhoColorFailed(false); // Clear the failure state for WhoColor data + setCheckArticleTitle(false); // Stop further title verification checks + fetchParsedArticle(); // Re-fetch the parsed article content with the current title + fetchWhocolorHtml(); // Re-fetch the WhoColor HTML for the article using the current title + } else if (crossCheckArticleTitle !== article.title) { + setFetched(false); // Indicate a loading state until the Redux store updates the new article title and the component re-renders + } + }; + + // Trigger the article title verification and data fetching process if a previous fetch failed + if (checkArticleTitle) { + verifyAndFetchArticle(); + } + + // These are mediawiki user ids, and don't necessarily match the dashboard // database user ids, so we must fetch them by username from the wiki. const fetchUserIds = () => { diff --git a/app/assets/javascripts/constants/articles.js b/app/assets/javascripts/constants/articles.js index 04885ee364..2451898bad 100644 --- a/app/assets/javascripts/constants/articles.js +++ b/app/assets/javascripts/constants/articles.js @@ -7,3 +7,4 @@ export const UPDATE_ARTICLE_TRACKED_STATUS = 'UPDATE_ARTICLE_TRACKED_STATUS'; export const SET_ARTICLES_PAGE = 'SET_ARTICLES_PAGE'; export const ARTICLES_PER_PAGE = 100; export const RESET_PAGES = 'RESET_PAGES'; +export const UPDATE_ARTICLE_TITLE_AND_URL = 'UPDATE_ARTICLE_TITLE_AND_URL'; diff --git a/app/assets/javascripts/reducers/articles.js b/app/assets/javascripts/reducers/articles.js index ace7c924da..93ad2010a5 100644 --- a/app/assets/javascripts/reducers/articles.js +++ b/app/assets/javascripts/reducers/articles.js @@ -9,7 +9,8 @@ import { UPDATE_ARTICLE_TRACKED_STATUS, SET_ARTICLES_PAGE, ARTICLES_PER_PAGE, - RESET_PAGES + RESET_PAGES, + UPDATE_ARTICLE_TITLE_AND_URL, } from '../constants'; const initialState = { @@ -147,6 +148,16 @@ export default function articles(state = initialState, action) { return { ...state, currentPage: 1, totalPages: action.totalPages }; } + case UPDATE_ARTICLE_TITLE_AND_URL: { + return { + ...state, + articles: _.map(state.articles, article => + ((action.payload.articleId === article.id) + ? { ...article, title: action.payload.title, url: action.payload.url } + : article)) + }; + } + default: return state; } diff --git a/app/views/courses/articles.json.jbuilder b/app/views/courses/articles.json.jbuilder index e9da0c4f5f..ab0fab3363 100644 --- a/app/views/courses/articles.json.jbuilder +++ b/app/views/courses/articles.json.jbuilder @@ -3,14 +3,12 @@ json.course do json.articles @course.articles_courses.includes(article: :wiki).limit(@limit) do |ac| article = ac.article - json.call(ac, :character_sum, :references_count, :view_count, :new_article, :tracked) - json.call(article, :id, :namespace, :rating, :deleted) + json.call(ac, :character_sum, :references_count, :view_count, :new_article, :tracked, :user_ids) + json.call(article, :id, :namespace, :rating, :deleted, :mw_page_id, :url) json.title article.full_title json.language article.wiki.language json.project article.wiki.project - json.url article.url json.rating_num rating_priority(article.rating) json.pretty_rating rating_display(article.rating) - json.user_ids ac.user_ids end end From d8d46eb0b08b0bab6914de28f558c99e3d4a040a Mon Sep 17 00:00:00 2001 From: Abishek Das Date: Tue, 31 Dec 2024 22:19:36 +0530 Subject: [PATCH 10/16] fix-failing-test: apply 10-second wait for article viewer spec tests --- spec/features/article_viewer_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/article_viewer_spec.rb b/spec/features/article_viewer_spec.rb index b1386fe8f8..f585be486d 100644 --- a/spec/features/article_viewer_spec.rb +++ b/spec/features/article_viewer_spec.rb @@ -20,7 +20,7 @@ it 'shows list of students who edited the article' do visit "/courses/#{course.slug}/articles" find('button.icon-article-viewer').click - expect(page).to have_content("Edits by: \nRagesoss") + expect(page).to have_content("Edits by: \nRagesoss", wait: 10) # once authorship date loads within(:css, '.article-viewer-legend.user-legend-name.user-highlight-1', wait: 20) do # click to scroll to next highlight @@ -33,7 +33,7 @@ login_as(instructor) visit "/courses/#{course.slug}/articles" find('button.icon-article-viewer').click - expect(page).to have_content("Edits by: \nRagesoss") + expect(page).to have_content("Edits by: \nRagesoss", wait: 10) find('a', text: 'Quality Problems?').click fill_in 'submit-bad-work-alert', with: 'Something has gone terribly wrong' click_button 'Notify Wiki Expert' From d43dc2804d91327dbb6a071ed0e7401e9c4e43db Mon Sep 17 00:00:00 2001 From: 0x_!nit <113853868+shishiro26@users.noreply.github.com> Date: Wed, 1 Jan 2025 00:47:54 +0530 Subject: [PATCH 11/16] fix:issue with meeting dates extending beyond the ending date. (#6028) * Fix: Improve meeting date extraction and validation in Week component * Refactored the regex into a named-component * changed the date extraction to the course_date_utils.js * fixed the linting errors * Fix issue with meeting display after the end date * fix rspec test * fix rspec tests and check if meetings go beyond timeline.end --- .../javascripts/utils/course_date_utils.js | 7 ++++++- spec/features/course_overview_spec.rb | 20 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/utils/course_date_utils.js b/app/assets/javascripts/utils/course_date_utils.js index fb68397f65..7eedf49ae9 100644 --- a/app/assets/javascripts/utils/course_date_utils.js +++ b/app/assets/javascripts/utils/course_date_utils.js @@ -141,6 +141,11 @@ const CourseDateUtils = { for (const week of range(0, (courseWeeks - 1), true)) { weekStart = addWeeks(startOfWeek(toDate(course.timeline_start)), week); + let weekendDate = endOfWeek(toDate(weekStart)); + if (isAfter(weekendDate, toDate(course.end))) { + weekendDate = toDate(course.end); + } + // Account for the first partial week, which may not have 7 days. let firstDayOfWeek; if (week === 0) { @@ -153,7 +158,7 @@ const CourseDateUtils = { // eslint-disable-next-line no-restricted-syntax for (const i of range(firstDayOfWeek, 6, true)) { const day = addDays(weekStart, i); - if (course && this.courseMeets(course.weekdays, i, format(day, 'yyyyMMdd'), exceptions)) { + if (course && this.courseMeets(course.weekdays, i, format(day, 'yyyyMMdd'), exceptions) && !isAfter(day, weekendDate)) { ms.push(format(day, 'EEEE (MM/dd)')); } } diff --git a/spec/features/course_overview_spec.rb b/spec/features/course_overview_spec.rb index f6138eb025..176e2687e6 100644 --- a/spec/features/course_overview_spec.rb +++ b/spec/features/course_overview_spec.rb @@ -48,14 +48,17 @@ end context 'when course starts in future' do - let(:timeline_start) { '2025-02-11'.to_date + 2.weeks } # a Tuesday - + let(:course_start) { '2025-02-11'.to_date } + let(:course_end) { course_start + 6.months } + let(:timeline_start) { '2025-02-11'.to_date + 2.weeks } + let(:timeline_end) { course_end.to_date } + before do course.update(timeline_start:) visit "/courses/#{course.slug}" sleep 1 end - + it 'displays week activity for the first week' do within '.course__this-week' do expect(page).to have_content('First Active Week') @@ -63,9 +66,18 @@ end within '.week-range' do expect(page).to have_content(timeline_start.beginning_of_week(:sunday).strftime('%m/%d')) - # Class normally meets on Sun, W, Sat, but timeline starts on Tuesday. end within '.margin-bottom' do + meeting_dates = [ + Date.parse('2025-02-23'), # Sunday (02/23) + Date.parse('2025-02-26'), # Wednesday (02/26) + Date.parse('2025-03-01') # Saturday (03/01) + ] + + meeting_dates.each do |meeting_date| + expect(meeting_date).to be_between(timeline_start, timeline_end).or be_between(course_start, course_end) + end + expect(page).to have_content('Meetings: Wednesday (02/26), Saturday (03/01)') end within '.week-index' do From b382331c8d5ed24a2e6d4ec3666c8bb5e1d75941 Mon Sep 17 00:00:00 2001 From: empty-codes Date: Tue, 31 Dec 2024 20:27:40 +0100 Subject: [PATCH 12/16] Restructures contents + Adds details about 3rd party APIs and tools the dashboard relies on --- docs/admin_guide.md | 98 +++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/docs/admin_guide.md b/docs/admin_guide.md index 42879efb43..81b18e6d28 100644 --- a/docs/admin_guide.md +++ b/docs/admin_guide.md @@ -1,38 +1,39 @@ # Admin Guide for Program & Events Dashboard +The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://outreachdashboard.wmflabs.org)) is a web application designed to support the global Wikimedia community in organizing various programs, including edit-a-thons, education initiatives, and other events. See the **[Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboard/tree/wmflabs)** and **[Phabricator Project](https://phabricator.wikimedia.org/project/manage/1052/)** for more details. -This guide provides an overview of the Program & Events Dashboard. It also offers resources for managing and troubleshooting the system. +This guide provides an overview of the **Program & Events Dashboard** infrastructure, detailing the servers, tools, and third-party dependencies that power the system. It also provides resources for managing and troubleshooting the system. ## Table of Contents -1. [Overview](#overview) +1. [Infrastructure Overview](#infrastructure-overview) + - [Servers](#servers) + - [Integrated Toolforge Tools](#integrated-toolforge-tools) + - [Other Integrated APIs and Third-Party Dependencies](#other-integrated-apis-and-third-party-dependencies) 2. [Monitoring and Logs](#monitoring-and-logs) - [Toolforge](#toolforge) - [Cloud VPS](#cloud-vps) 3. [Troubleshooting](#troubleshooting) - - [Web Server Issues](#web-server-issues) - - [Database Issues](#database-issues) - - [Data Dumps and Recovery](#data-dumps-and-recovery) + - [Web Server Issues](#web-server-issues) + - [Database Issues](#database-issues) + - [Data Dumps and Recovery](#data-dumps-and-recovery) 4. [More Resources](#more-resources) -## Overview +## Infrastructure Overview +The **Program & Events Dashboard** is hosted within the **Wikimedia Cloud VPS** project [globaleducation](https://openstack-browser.toolforge.org/project/globaleducation), which provides the infrastructure for all servers, allowing the dashboard to run on virtual machines that are flexible and easily managed within Wikimedia Cloud. -The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://outreachdashboard.wmflabs.org)) is a web application designed to support the global Wikimedia community in organizing various programs, including edit-a-thons, education initiatives, and other events. **[Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboard/tree/wmflabs)** **[Phabricator Project](https://phabricator.wikimedia.org/project/manage/1052/)** - -### Infrastructure Overview -- **Toolforge Tool**: [wikiedudashboard](https://toolsadmin.wikimedia.org/tools/id/wikiedudashboard) - - **Source code: [WikiEduDashboardTools](https://github.com/WikiEducationFoundation/WikiEduDashboardTools)**: A collection of PHP endpoints that retrieve revision and article data from Wikimedia Replica databases in the Wikimedia Cloud environment. - - **Deployed at**: [wikiedudashboard.toolforge.org](https://wikiedudashboard.toolforge.org/), [replica-revision-tools.wmcloud.org](https://replica-revision-tools.wmcloud.org/) - -- **Cloud VPS Project**: [globaleducation](https://openstack-browser.toolforge.org/project/globaleducation) +The dashboard relies on several core servers and external tools to function. These components ensure that different tasks are isolated to avoid bottlenecks and improve system performance. ### Servers +The dashboard operates on a distributed server architecture to handle web requests, process background jobs, and store application data. Each server is dedicated to specific roles, minimizing competition for resources and improving reliability by isolating potential bottlenecks and failures. + +Below is a breakdown of the key servers and their roles within the infrastructure: 1. **Web Server** - **`peony-web.globaleducation.eqiad1.wikimedia.cloud`** - Hosts the main web application and core Sidekiq processes using **RVM (Ruby Version Manager)**, **Phusion Passenger**, and **Apache**. - **Capistrano** is used for deployments - Sidekiq processes hosted: - - `sidekiq-default`: Handles transactional jobs (e.g., wiki edits, email notifications). - - `sidekiq-constant`: Manages frequently run tasks (e.g., adding courses to update queues). + - `sidekiq-default`: Manages frequently run tasks (e.g., adding courses to update queues). + - `sidekiq-constant`: Handles transactional jobs (e.g., wiki edits, email notifications). - `sidekiq-daily`: Executes long-running daily update tasks. 2. **Sidekiq Servers**: These dedicated servers handle the other Sidekiq processes to isolate bottlenecks and failures: @@ -41,18 +42,68 @@ The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://out - **`peony-sidekiq-3.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-long` for long-running course updates with higher queue latency. 3. **Database Server** - - **`peony-database.globaleducation.eqiad1.wikimedia.cloud`**: Manages the Dashboard's primary database. + - **`peony-database.globaleducation.eqiad1.wikimedia.cloud`**: Stores program, user, and revision data. It supports the dashboard’s data queries and updates. 4. **Redis Server** - - **`p-and-e-dashboard-redis.globaleducation.eqiad1.wikimedia.cloud`**: Shared across all Sidekiq processes for task queuing and caching. + - **`p-and-e-dashboard-redis.globaleducation.eqiad1.wikimedia.cloud`**: Stores all task (job) details and is shared across all Sidekiq processes for task queuing and caching. + +### Integrated Toolforge Tools + +- **[wikiedudashboard](https://toolsadmin.wikimedia.org/tools/id/wikiedudashboard)** + A collection of PHP endpoints used to retrieve revision and article data from Wikimedia Replica databases. + See [replica.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/replica.rb) for an example of its usage. + **[[Live Tool](https://replica-revision-tools.wmcloud.org/), [Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboardTools)]** + +- **[Reference Counter API](https://toolsadmin.wikimedia.org/tools/id/reference-counter)** + Flask API to count the number of existing references in a specified revision ID for a Wiki. This API has two main endpoints to retrieve number of references for a given revision, one using [wikitext](https://gitlab.wikimedia.org/toolforge-repos/reference-counter#based-on-wikitext), the other [using HTML](https://gitlab.wikimedia.org/toolforge-repos/reference-counter#based-on-html). + See [reference_counter_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/reference_counter_api.rb) for an example of its usage. + **[[Live Tool](https://reference-counter.toolforge.org/), [Source Code](https://gitlab.wikimedia.org/toolforge-repos/reference-counter), [Phabricator Documentation](https://phabricator.wikimedia.org/T352177)]** + +- **[Suspected Plagiarism API](https://toolsadmin.wikimedia.org/tools/id/ruby-suspected-plagiarism)** + API for fetching recent suspected plagiarism detected by CopyPatrol and accessing Turnitin reports. + See [plagiabot_importer.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/importers/plagiabot_importer.rb) for an example of its usage. + **[[Live Tool](https://ruby-suspected-plagiarism.toolforge.org/), [Source Code](https://github.com/WikiEducationFoundation/ruby-suspected-plagiarism)]** + +- **[Copypatrol](https://toolsadmin.wikimedia.org/tools/id/copypatrol)** + A plagiarism detection tool, that allows you to see recent Wikipedia edits that are flagged as possible copyright violations. It serves as the database for the ruby-suspected-plagiarism tool. + **[[Live Tool](https://copypatrol.wmcloud.org/en), [Source Code](https://github.com/wikimedia/CopyPatrol/), [Documentation](https://meta.wikimedia.org/wiki/CopyPatrol), [Phabricator Project](https://phabricator.wikimedia.org/project/profile/1638/)]** + +- **[PagePile](https://toolsadmin.wikimedia.org/tools/id/pagepile)** + Manages static lists of Wiki pages. You can use a PetScan query (among other options) to create a PagePile, essentially creating a permanent snapshot of the PetScan query results. You can also create a PagePile from a simple one-per-line text list of article titles. + See [pagepile_scoping.jsx](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/course_creator/scoping_methods/pagepile_scoping.jsx) and [pagepile_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/pagepile_api.rb) for examples of its usage. + **[[Live Tool](https://pagepile.toolforge.org/), [Source Code](https://bitbucket.org/magnusmanske/pagepile/src/master/), [Documentation](https://pagepile.toolforge.org/howto.html)]** +### Other Integrated APIs and Third-Party Dependencies + +- **[PetScan](https://petscan.wmcloud.org/)** + A powerful tool that can assemble lists of articles using a wide variety of data sources (including categories and templates, as well incoming and outgoing links, Wikidata relationships, and more). Users create a query on the PetScan website, which returns a PSID for that query, and that PSID is how the Dashboard connects to the PetScan API to get the list of articles. PetScan queries are dynamic; while the query for a given PSID cannot be modified, the results may change each time the the query is run, based on changes that happened on Wikipedia and Wikidata. + See [petscan_scoping.jsx](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/course_creator/scoping_methods/petscan_scoping.jsx) and [petscan_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/petscan_api.rb#L5) for examples of its usage. + **[[Source Code](https://github.com/magnusmanske/petscan_rs), [Documentation](https://meta.wikimedia.org/wiki/PetScan/en)]** + +- **[WikiWho API](https://wikiwho-api.wmcloud.org/en/api/v1.0.0-beta/)** + Set of APIs to parse historical revisions of Wikipedia articles, providing detailed provenance of each token (word) in terms of who added, removed, or reintroduced it across different revisions. + See [`ArticleViewerAPI.js`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/ArticleViewerAPI.js#L96) and the [`wikiwhoColorURL`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/URLBuilder.js#L35) for examples of its usage. + **[[Source Code](https://github.com/wikimedia/wikiwho_api), [Documentation](https://wikiwho-api.wmcloud.org/gesis_home)]** + +- **[WhoColor API](https://wikiwho-api.wmcloud.org/en/whocolor/v1.0.0-beta/)** + Set of APIs built on top of the WikiWho API that allow for the visualization of authorship data by color-coding tokens in the text based on their original authors. The dashboard employs this to show authorship data on its dashboard for students. + **[[Source Code](https://github.com/wikimedia/wikiwho_api), [Documentation](https://wikiwho-api.wmcloud.org/gesis_home)]** + +- **[WikidataDiffAnalyzer](https://github.com/WikiEducationFoundation/wikidata-diff-analyzer)** + Ruby gem for analyzing differences between revisions. + See [update_wikidata_stats.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/services/update_wikidata_stats.rb#L91) for an example of its usage. + **[[Source Code and Documentation](https://github.com/WikiEducationFoundation/wikidata-diff-analyzer)]** + +- **[Liftwing API](https://api.wikimedia.org/wiki/Lift_Wing_API/Reference)** + Makes predictions about pages and edits using machine learning. The dashboard uses this API to fetch items and article quality data. + See [article_finder_action.js](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/actions/article_finder_action.js#L18) and [lift_wing_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/lift_wing_api.rb#L8) for examples of its usage. + **[[Source Code](https://gerrit.wikimedia.org/g/machinelearning/liftwing/), [Documentation](https://api.wikimedia.org/wiki/Lift_Wing_API), [Phabricator Project](https://phabricator.wikimedia.org/project/profile/5020/)]** + ## Monitoring and Logs #### Toolforge -- [Kubernetes Namespace Details](https://k8s-status.toolforge.org/namespaces/tool-wikiedudashboard/) -- [Kubernetes Pod Details](https://k8s-status.toolforge.org/namespaces/tool-wikiedudashboard/pods/wikiedudashboard-5954f86c86-pm8d5/) - +To view Kubernetes namespace details for a Toolforge tool, go to https://k8s-status.toolforge.org/namespaces/tool-toolName/, replacing `toolName` with the name of the tool. #### Cloud VPS - [Grafana](https://grafana.wmcloud.org/d/0g9N-7pVz/cloud-vps-project-board?orgId=1&var-project=globaleducation) @@ -60,7 +111,6 @@ The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://out - [Alerts](https://prometheus-alerts.wmcloud.org/?q=%40state%3Dactive&q=project%3Dglobaleducation) - [Puppet agent logs for the globaleducation project](https://grafana.wmcloud.org/d/SQM7MJZSz/cloud-vps-puppet-agents?orgId=1&var-project=globaleducation&from=now-2d&to=now) - ## Troubleshooting ### Web Server Issues @@ -86,9 +136,7 @@ The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://out 3. Once the dump is complete, drop the table. 4. Remove the database from recovery mode and restore the table. -### Third-Party Dependencies -Issues could also be caused by maintenance or outages in third-party dependencies such as Openstack, Toolforge, or other services. - +Issues could also be caused by maintenance or outages in third-party dependencies or other services stated above. ## More Resources - [Toolforge Documentation](https://wikitech.wikimedia.org/wiki/Help:Toolforge) From 14e2da51531b496e5a1db08aa48e258380f56022 Mon Sep 17 00:00:00 2001 From: Sage Ross Date: Tue, 31 Dec 2024 12:05:47 -0800 Subject: [PATCH 13/16] Fix linting violations --- spec/features/course_overview_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/features/course_overview_spec.rb b/spec/features/course_overview_spec.rb index 176e2687e6..5bcb827e54 100644 --- a/spec/features/course_overview_spec.rb +++ b/spec/features/course_overview_spec.rb @@ -52,13 +52,13 @@ let(:course_end) { course_start + 6.months } let(:timeline_start) { '2025-02-11'.to_date + 2.weeks } let(:timeline_end) { course_end.to_date } - + before do course.update(timeline_start:) visit "/courses/#{course.slug}" sleep 1 end - + it 'displays week activity for the first week' do within '.course__this-week' do expect(page).to have_content('First Active Week') @@ -73,11 +73,13 @@ Date.parse('2025-02-26'), # Wednesday (02/26) Date.parse('2025-03-01') # Saturday (03/01) ] - - meeting_dates.each do |meeting_date| - expect(meeting_date).to be_between(timeline_start, timeline_end).or be_between(course_start, course_end) + + meeting_dates.each do |meeting_date| # rubocop:disable RSpec/IteratedExpectation + expect(meeting_date) + .to be_between(timeline_start, timeline_end) + .or be_between(course_start, course_end) end - + expect(page).to have_content('Meetings: Wednesday (02/26), Saturday (03/01)') end within '.week-index' do From 5ba44516ab02c7a6c0014c9cbd413bf73c217ebb Mon Sep 17 00:00:00 2001 From: empty-codes Date: Tue, 31 Dec 2024 23:09:27 +0100 Subject: [PATCH 14/16] Updates tool descriptions to focus on how the Dashboard uses each + Minor formatting edits/ corrections --- docs/admin_guide.md | 46 +++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/admin_guide.md b/docs/admin_guide.md index 81b18e6d28..88d24eff4c 100644 --- a/docs/admin_guide.md +++ b/docs/admin_guide.md @@ -37,9 +37,9 @@ Below is a breakdown of the key servers and their roles within the infrastructur - `sidekiq-daily`: Executes long-running daily update tasks. 2. **Sidekiq Servers**: These dedicated servers handle the other Sidekiq processes to isolate bottlenecks and failures: - - **`peony-sidekiq.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-short` for short-running course updates. + - **`peony-sidekiq.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-long` for long-running course updates with higher queue latency. - **`peony-sidekiq-medium.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-medium` for typical course updates. - - **`peony-sidekiq-3.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-long` for long-running course updates with higher queue latency. + - **`peony-sidekiq-3.globaleducation.eqiad1.wikimedia.cloud`**: Hosts `sidekiq-short` for short-running course updates. 3. **Database Server** - **`peony-database.globaleducation.eqiad1.wikimedia.cloud`**: Stores program, user, and revision data. It supports the dashboard’s data queries and updates. @@ -50,54 +50,56 @@ Below is a breakdown of the key servers and their roles within the infrastructur ### Integrated Toolforge Tools - **[wikiedudashboard](https://toolsadmin.wikimedia.org/tools/id/wikiedudashboard)** - A collection of PHP endpoints used to retrieve revision and article data from Wikimedia Replica databases. - See [replica.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/replica.rb) for an example of its usage. - **[[Live Tool](https://replica-revision-tools.wmcloud.org/), [Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboardTools)]** + The Dashboard uses this tool's PHP endpoints to query Wikimedia Replica databases for detailed revision and article data. The specific replica database the tool connects to is dependent on the wiki being queried. These endpoints support features like retrieving user contributions, identifying existing articles or revisions, and checking for deleted content. For example, the Dashboard uses the `/revisions.php` endpoint to fetch revisions by specific users within a time range, and `/articles.php` to verify the existence of articles or revisions. See [replica.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/replica.rb) for implementation details. + + **[[Live Tool](https://replica-revision-tools.wmcloud.org/), [Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboardTools)]** - **[Reference Counter API](https://toolsadmin.wikimedia.org/tools/id/reference-counter)** - Flask API to count the number of existing references in a specified revision ID for a Wiki. This API has two main endpoints to retrieve number of references for a given revision, one using [wikitext](https://gitlab.wikimedia.org/toolforge-repos/reference-counter#based-on-wikitext), the other [using HTML](https://gitlab.wikimedia.org/toolforge-repos/reference-counter#based-on-html). - See [reference_counter_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/reference_counter_api.rb) for an example of its usage. + The Reference Counter API is used to retrieve the number of references in a specified revision ID from a Wiki. The Dashboard interacts with the API through the [`ReferenceCounterApi`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/reference_counter_api.rb) class, which handles requests for reference counts by revision ID and processes multiple revisions in batch. It's important to note that the `ReferenceCounterApi` class and the `reference-counter` Toolforge API do not support Wikidata, as it uses a different method for calculating references. + **[[Live Tool](https://reference-counter.toolforge.org/), [Source Code](https://gitlab.wikimedia.org/toolforge-repos/reference-counter), [Phabricator Documentation](https://phabricator.wikimedia.org/T352177)]** - **[Suspected Plagiarism API](https://toolsadmin.wikimedia.org/tools/id/ruby-suspected-plagiarism)** - API for fetching recent suspected plagiarism detected by CopyPatrol and accessing Turnitin reports. - See [plagiabot_importer.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/importers/plagiabot_importer.rb) for an example of its usage. + This API is used to detect and report suspected plagiarism in course-related content. It leverages CopyPatrol to detect instances of potential plagiarism by comparing revisions of Wikipedia articles. The API then retrieves data on suspected plagiarism, which includes information such as the revision ID, the user responsible, and the article involved. The [`PlagiabotImporter`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/importers/plagiabot_importer.rb) class uses this data to identify recent instances of suspected plagiarism and match them with relevant revisions in the Dashboard's database. If a new case is found, an alert is generated for suspected plagiarism in course materials and sent to content experts for review. + **[[Live Tool](https://ruby-suspected-plagiarism.toolforge.org/), [Source Code](https://github.com/WikiEducationFoundation/ruby-suspected-plagiarism)]** - **[Copypatrol](https://toolsadmin.wikimedia.org/tools/id/copypatrol)** - A plagiarism detection tool, that allows you to see recent Wikipedia edits that are flagged as possible copyright violations. It serves as the database for the ruby-suspected-plagiarism tool. + A plagiarism detection tool, that allows you to see recent Wikipedia edits that are flagged as possible copyright violations. It is responsible for detecting instances of potential plagiarism by comparing revisions of Wikipedia articles. + **[[Live Tool](https://copypatrol.wmcloud.org/en), [Source Code](https://github.com/wikimedia/CopyPatrol/), [Documentation](https://meta.wikimedia.org/wiki/CopyPatrol), [Phabricator Project](https://phabricator.wikimedia.org/project/profile/1638/)]** - **[PagePile](https://toolsadmin.wikimedia.org/tools/id/pagepile)** - Manages static lists of Wiki pages. You can use a PetScan query (among other options) to create a PagePile, essentially creating a permanent snapshot of the PetScan query results. You can also create a PagePile from a simple one-per-line text list of article titles. - See [pagepile_scoping.jsx](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/course_creator/scoping_methods/pagepile_scoping.jsx) and [pagepile_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/pagepile_api.rb) for examples of its usage. + PagePile manages static lists of Wiki pages. The Dashboard utilizes it to fetch a permanent snapshot of article titles through PagePile IDs or URLs. This is integrated into the course creation process, where users can input PagePile IDs or URLs to define a set of articles for the course. The [`PagePileApi`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/pagepile_api.rb) class is responsible for retrieving page titles from PagePile, ensuring the category's wiki is consistent with the PagePile data, and updating the system with the retrieved titles. The data is then used to scope course materials to specific articles - see [pagepile_scoping.jsx](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/course_creator/scoping_methods/pagepile_scoping.jsx). + **[[Live Tool](https://pagepile.toolforge.org/), [Source Code](https://bitbucket.org/magnusmanske/pagepile/src/master/), [Documentation](https://pagepile.toolforge.org/howto.html)]** ### Other Integrated APIs and Third-Party Dependencies - **[PetScan](https://petscan.wmcloud.org/)** - A powerful tool that can assemble lists of articles using a wide variety of data sources (including categories and templates, as well incoming and outgoing links, Wikidata relationships, and more). Users create a query on the PetScan website, which returns a PSID for that query, and that PSID is how the Dashboard connects to the PetScan API to get the list of articles. PetScan queries are dynamic; while the query for a given PSID cannot be modified, the results may change each time the the query is run, based on changes that happened on Wikipedia and Wikidata. - See [petscan_scoping.jsx](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/course_creator/scoping_methods/petscan_scoping.jsx) and [petscan_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/petscan_api.rb#L5) for examples of its usage. - **[[Source Code](https://github.com/magnusmanske/petscan_rs), [Documentation](https://meta.wikimedia.org/wiki/PetScan/en)]** + The PetScan API is used in the Dashboard to integrate dynamic lists of articles based on user-defined queries. Users can enter PetScan IDs (PSIDs) or URLs to fetch a list of articles relevant to a course. The [`PetScanApi`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/petscan_api.rb#L5) class handles retrieving the list of page titles associated with a given PSID by querying PetScan's API. This data is used for scoping course materials to specific sets of articles - see [petscan_scoping.jsx](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/course_creator/scoping_methods/petscan_scoping.jsx), ensuring the Dashboard reflects the most up-to-date information from PetScan queries. The system ensures proper error handling for invalid or unreachable PSIDs to avoid disrupting the course creation process. + + **[[Source Code](https://github.com/magnusmanske/petscan_rs), [Documentation](https://meta.wikimedia.org/wiki/PetScan/en)]** - **[WikiWho API](https://wikiwho-api.wmcloud.org/en/api/v1.0.0-beta/)** - Set of APIs to parse historical revisions of Wikipedia articles, providing detailed provenance of each token (word) in terms of who added, removed, or reintroduced it across different revisions. - See [`ArticleViewerAPI.js`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/ArticleViewerAPI.js#L96) and the [`wikiwhoColorURL`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/URLBuilder.js#L35) for examples of its usage. + The WikiWho API is used in the Dashboard to parse historical revisions of Wikipedia articles and track the provenance of each word in the article. This data is particularly useful for displaying authorship information, such as identifying who added, removed, or reintroduced specific tokens (words) across different revisions. The [`URLBuilder`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/URLBuilder.js#L35) class constructs the necessary URLs to interact with the WikiWho API, allowing the Dashboard to fetch parsed article data and token-level authorship highlights. This data is then used in the [`ArticleViewer`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/ArticleViewerAPI.js#L96) component to enhance the display of articles by showing detailed authorship information, providing insights into the contributions of different editors over time. + **[[Source Code](https://github.com/wikimedia/wikiwho_api), [Documentation](https://wikiwho-api.wmcloud.org/gesis_home)]** - **[WhoColor API](https://wikiwho-api.wmcloud.org/en/whocolor/v1.0.0-beta/)** - Set of APIs built on top of the WikiWho API that allow for the visualization of authorship data by color-coding tokens in the text based on their original authors. The dashboard employs this to show authorship data on its dashboard for students. + The WhoColor API is used in the Dashboard to add color-coding to the authorship data provided by the WikiWho API. It enhances the parsed article revisions by highlighting each token (word) with a color corresponding to its original author, making it easier to visualize contributions. The Dashboard processes this color-coded data by using the [`highlightAuthors`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/containers/ArticleViewer.jsx#L163) function, which replaces the span elements in the HTML with styled versions that include user-specific color classes. This allows the [`ArticleViewer`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/components/common/ArticleViewer/utils/ArticleViewerAPI.js#L96) component to display the article text with visual cues, highlighting which user contributed each part of the article, helping quick identification of the contributions of different authors. + **[[Source Code](https://github.com/wikimedia/wikiwho_api), [Documentation](https://wikiwho-api.wmcloud.org/gesis_home)]** - **[WikidataDiffAnalyzer](https://github.com/WikiEducationFoundation/wikidata-diff-analyzer)** - Ruby gem for analyzing differences between revisions. - See [update_wikidata_stats.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/services/update_wikidata_stats.rb#L91) for an example of its usage. + The WikidataDiffAnalyzer gem is used to analyze differences between Wikidata revisions. It is utilized by the [`update_wikidata_stats.rb`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/services/update_wikidata_stats.rb#L91) service to process a list of revision IDs and determine the changes made between them, such as diffs added, removed, or changed claims, references, and labels. The results of the analysis are serialized and stored in the summary field of Wikidata revisions, providing detailed statistics about the nature of the edits. This enables the Dashboard to track and display revision-level changes. + **[[Source Code and Documentation](https://github.com/WikiEducationFoundation/wikidata-diff-analyzer)]** - **[Liftwing API](https://api.wikimedia.org/wiki/Lift_Wing_API/Reference)** - Makes predictions about pages and edits using machine learning. The dashboard uses this API to fetch items and article quality data. - See [article_finder_action.js](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/actions/article_finder_action.js#L18) and [lift_wing_api.rb](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/lift_wing_api.rb#L8) for examples of its usage. + The Liftwing API is used to fetch article quality and item quality data by making predictions about pages and edits using machine learning models. The Dashboard interacts with this API to assess the quality of articles and revisions, utilizing the [`LiftWingApi`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/lib/lift_wing_api.rb#L8) service to retrieve scores and features associated with each revision. The [`article_finder_action.js`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/actions/article_finder_action.js#L18) class is responsible for fetching and processing article data. It takes the revision IDs from fetched revision data and sends them to the LiftWing API for processing by calling the [`fetchPageRevisionScore`](https://github.com/WikiEducationFoundation/WikiEduDashboard/blob/wmflabs/app/assets/javascripts/actions/article_finder_action.js#L180) function. The LiftWing API then processes the revision data and returns the quality scores for the articles. + **[[Source Code](https://gerrit.wikimedia.org/g/machinelearning/liftwing/), [Documentation](https://api.wikimedia.org/wiki/Lift_Wing_API), [Phabricator Project](https://phabricator.wikimedia.org/project/profile/5020/)]** ## Monitoring and Logs From efc5e329e7110a3d4e8619b4804962232f2ad4c8 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 2 Jan 2025 13:20:45 +0100 Subject: [PATCH 15/16] Localisation updates from https://translatewiki.net. --- config/locales/de.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/locales/de.yml b/config/locales/de.yml index 84c3af0700..3d4be0d9fc 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -18,6 +18,7 @@ # Author: Metalhead64 # Author: Ngschaider # Author: Sebastian Wallroth +# Author: SergeCroise # Author: Taresi # Author: ThisCarthing # Author: ToBeFree @@ -1598,4 +1599,10 @@ de: title: Titel cancel_note_creation: Abbrechen save_note: Speichern + weekday_picker: + aria: + weekday_select: '{{weekday}} Drücken Sie die Eingabetaste, um auszuwählen' + weekday_selected: '{{weekday}} ausgewählt Drücken Sie die Eingabetaste, um die + Auswahl aufzuheben' + weekday_unselected: '{{weekday}} Nicht ausgewählt' ... From 3d3a960857573fb96e9ca41e68be0e99c8178259 Mon Sep 17 00:00:00 2001 From: empty-codes Date: Fri, 3 Jan 2025 00:13:13 +0100 Subject: [PATCH 16/16] Adds links to admin guide in relevant docs + Add link to main README in admin guide --- CONTRIBUTING.md | 1 + README.md | 1 + docs/README.md | 1 + docs/admin_guide.md | 5 ++++- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 213f0572de..f593168b15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,7 @@ If you're a new developer and you're looking for an easy way to get involved, tr - [MediaWiki API Sandbox](https://en.wikipedia.org/wiki/Special%3aApiSandbox) - [Quarry](http://quarry.wmflabs.org/): Public querying interface for the Labs replica database. Very useful for testing SQL queries and for figuring out what data is available. - [Guide to the front end](docs/frontend.md) +- [Admin Guide](docs/admin_guide.md): Overview of the Dashboard infrastructure, including servers, tools, dependencies, and troubleshooting resources. - [Vagrant](https://github.com/marxarelli/wikied-vagrant): a configuration to quickly get a development environment up and running using Vagrant. If you already have VirtualBox and/or Vagrant on your machine, this might be a simple way to set up a dev environment. However, it is not actively maintained. If you try it and run into problems, let us know! #### Code Style diff --git a/README.md b/README.md index a37f079398..9713a0bb3c 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ This project welcomes contributions, and we try to be as newbie-friendly as poss - [Interface strings & Internationalization](docs/i18n.md) - [OAuth setup](docs/oauth.md) - [Deployment](docs/deploy.md) +- [Admin Guide](docs/admin_guide.md) - Overview of the Dashboard infrastructure, including servers, tools, dependencies, and troubleshooting resources. - [Tools & Integrations](docs/tools.md) - [Using Docker for development](docs/docker.md) - [Model diagram](erd.pdf) diff --git a/docs/README.md b/docs/README.md index 45b1264fb4..37a17ef274 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ - [Contributing](../CONTRIBUTING.md) - [Development Process outline](dev_process.md) - [Deployment](deploy.md) +- [Admin Guide](docs/admin_guide.md) - [Upgrading dependencies](upgrade_dependencies.md) - [Tools & Integrations](tools.md) - [Model diagram](../erd.pdf) diff --git a/docs/admin_guide.md b/docs/admin_guide.md index 88d24eff4c..1440d47bf6 100644 --- a/docs/admin_guide.md +++ b/docs/admin_guide.md @@ -1,4 +1,7 @@ -# Admin Guide for Program & Events Dashboard +[Back to README](../README.md) + +## Admin Guide for Program & Events Dashboard + The **Programs & Events Dashboard** ([outreachdashboard.wmflabs.org](https://outreachdashboard.wmflabs.org)) is a web application designed to support the global Wikimedia community in organizing various programs, including edit-a-thons, education initiatives, and other events. See the **[Source Code](https://github.com/WikiEducationFoundation/WikiEduDashboard/tree/wmflabs)** and **[Phabricator Project](https://phabricator.wikimedia.org/project/manage/1052/)** for more details. This guide provides an overview of the **Program & Events Dashboard** infrastructure, detailing the servers, tools, and third-party dependencies that power the system. It also provides resources for managing and troubleshooting the system.