diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 95f823c302..fed3cf9e8d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -81,14 +81,6 @@ jobs:
coverageCommand: bundle exec rspec spec/ --color --profile --format documentation
coverageLocations: |
${{github.workspace}}/public/js_coverage/lcov.info:lcov
-
- - name: Archive capybara failure screenshots
- uses: actions/upload-artifact@v4
- if: failure()
- with:
- name: dist-without-markdown
- path: tmp/capybara/*.png
- if-no-files-found: ignore
- name: Ruby linting
run: bundle exec rubocop
diff --git a/.rubocop.yml b/.rubocop.yml
index 80144a1092..9fe6c64747 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -123,6 +123,8 @@ Rails/FilePath:
Enabled: false
Performance/UnfreezeString:
Enabled: false
+Style/GuardClause:
+ Enabled: false
Rails/HasManyOrHasOneDependent:
Enabled: false
Rails/InverseOf:
diff --git a/app/assets/javascripts/actions/revisions_actions.js b/app/assets/javascripts/actions/revisions_actions.js
index 918c1bf3ab..7cc9d4af07 100644
--- a/app/assets/javascripts/actions/revisions_actions.js
+++ b/app/assets/javascripts/actions/revisions_actions.js
@@ -1,3 +1,4 @@
+
import {
RECEIVE_REVISIONS,
REVISIONS_LOADING,
@@ -34,19 +35,12 @@ const fetchAllArticles = async (course) => {
const fetchRevisionsPromise = async (course, users, last_date, dispatch) => {
const { revisions, last_date: new_last_date } = await fetchRevisionsFromUsers(course, users, 7, last_date);
+ course.revisions = sortRevisionsByDate(revisions);
- // Create a new course object with updated revisions
- const updatedCourse = {
- ...course,
- revisions: sortRevisionsByDate(revisions),
- };
-
- // we don't await this. When the assessments/references get loaded, the action is dispatched
+ // we don't await this. When the assessments/references get laoded, the action is dispatched
fetchRevisionsAndReferences(revisions, dispatch);
-
- return { course: updatedCourse, last_date: new_last_date };
+ return { course, last_date: new_last_date };
};
-
const fetchRevisionsCourseSpecificPromise = async (course, users, last_date, dispatch, articles) => {
const trackedArticles = new Set(
articles.filter(article => article.tracked).map(article => article.title)
diff --git a/app/assets/javascripts/actions/uploads_actions.js b/app/assets/javascripts/actions/uploads_actions.js
index c0cf2c306a..a15da975c5 100644
--- a/app/assets/javascripts/actions/uploads_actions.js
+++ b/app/assets/javascripts/actions/uploads_actions.js
@@ -10,34 +10,25 @@ const fetchUploads = (courseId) => {
if (res.ok && res.status === 200) {
return res.json();
}
- return Promise.reject(new Error(`Failed to fetch uploads. Status: ${res.status}`));
+ return Promise.reject(res);
})
.catch((error) => {
logErrorMessage(error);
- return { error: 'Failed to fetch uploads', status: error.status || 500 };
});
};
export const receiveUploads = courseId => (dispatch) => {
- return fetchUploads(courseId)
- .then((resp) => {
- if (!resp) {
- return dispatch({
- type: API_FAIL,
- data: { error: 'No response received' },
- });
- }
- return dispatch({
+ return (
+ fetchUploads(courseId)
+ .then(resp => dispatch({
type: RECEIVE_UPLOADS,
data: resp,
- });
- })
- .catch((resp) => {
- dispatch({
+ }))
+ .catch(resp => dispatch({
type: API_FAIL,
- data: resp,
- });
- });
+ data: resp
+ }))
+ );
};
const fetchUploadMetadata = (uploads) => {
diff --git a/app/assets/javascripts/components/activity/activity_table.jsx b/app/assets/javascripts/components/activity/activity_table.jsx
new file mode 100644
index 0000000000..f91f1e6edc
--- /dev/null
+++ b/app/assets/javascripts/components/activity/activity_table.jsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
+import { flatten, zip } from 'lodash-es';
+import { formatDateWithTime } from '../../utils/date_utils';
+import ActivityTableRow from './activity_table_row.jsx';
+import Loading from '../common/loading.jsx';
+
+const ActivityTable = ({ onSort, activity, headers, loading, noActivityMessage }) => {
+ const openKey = useSelector(state => state.ui.openKey);
+
+ const sortItems = (e) => {
+ onSort(e.currentTarget.getAttribute('data-sort-key'));
+ };
+
+ const _renderActivites = () => {
+ return activity.map((revision) => {
+ const roundedRevisionScore = Math.round(revision.revision_score) || 'unknown';
+ const revisionDateTime = formatDateWithTime(revision.datetime);
+ const talkPageLink = `${revision.base_url}/wiki/User_talk:${revision.username}`;
+ const isOpen = openKey === `drawer_${revision.key}`;
+
+ return (
+
+ );
+ });
+ };
+
+ const _renderDrawers = () => {
+ return activity.map((revision) => {
+ const courses = revision.courses.map((course) => {
+ return (
+
+ {course.title}
+
+ );
+ });
+
+ return (
+
+
+
+
+
+
+
+
+ {I18n.t('recent_activity.active_courses')}
+
+
+ |
+
+
+
+ |
+
+ );
+ });
+ };
+
+ const _renderHeaders = () => {
+ return headers.map((header) => {
+ return (
+
+ {header.title}
+
+ |
+ );
+ });
+ };
+
+ if (loading) {
+ return ;
+ }
+
+ const renderedActivity = _renderActivites();
+ const drawers = _renderDrawers();
+ const ths = _renderHeaders();
+
+ let elements = flatten(zip(renderedActivity, drawers));
+ if (!elements.length) {
+ elements = {noActivityMessage} |
;
+ }
+
+ return (
+
+
+
+ {ths}
+ |
+
+
+
+ {elements}
+
+
+ );
+};
+
+ActivityTable.propTypes = {
+ loading: PropTypes.bool,
+ activity: PropTypes.array,
+ headers: PropTypes.array,
+ noActivityMessage: PropTypes.string,
+ toggleDrawer: PropTypes.func
+};
+
+export default (ActivityTable);
diff --git a/app/assets/javascripts/components/activity/activity_table_row.jsx b/app/assets/javascripts/components/activity/activity_table_row.jsx
new file mode 100644
index 0000000000..5ac6155d2e
--- /dev/null
+++ b/app/assets/javascripts/components/activity/activity_table_row.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import DiffViewer from '../revisions/diff_viewer.jsx';
+import { toggleUI } from '../../actions';
+import { useDispatch } from 'react-redux';
+
+const ActivityTableRow = ({ isOpen, diffUrl, revisionDateTime,
+ revisionScore, reportUrl, revision, rowId, articleUrl, title, talkPageLink,
+ author }) => {
+ const dispatch = useDispatch();
+
+ const openDrawer = () => {
+ return dispatch(toggleUI(`drawer_${rowId}`));
+ };
+
+ let revDateElement;
+ let col2;
+ const className = isOpen ? 'open' : 'closed';
+
+ if (diffUrl) {
+ revDateElement = (
+ {revisionDateTime}
+ );
+ }
+
+ if (revisionScore) {
+ col2 = (
+
+ {revisionScore}
+ |
+ );
+ }
+
+ if (reportUrl) {
+ col2 = (
+
+ {I18n.t('recent_activity.report')}
+ |
+ );
+ }
+
+ let diffViewer;
+ if (revision && revision.api_url) {
+ diffViewer = ;
+ }
+
+ return (
+
+
+ {title}
+ |
+ {col2}
+
+ {author}
+ |
+
+ {revDateElement}
+ |
+
+ {diffViewer}
+ |
+
+ );
+};
+
+ActivityTableRow.propTypes = {
+ rowId: PropTypes.number,
+ diffUrl: PropTypes.string,
+ revisionDateTime: PropTypes.string,
+ reportUrl: PropTypes.string,
+ revisionScore: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.number
+ ]),
+ articleUrl: PropTypes.string,
+ talkPageLink: PropTypes.string,
+ author: PropTypes.string,
+ title: PropTypes.string,
+ revision: PropTypes.object,
+ isOpen: PropTypes.bool,
+};
+
+export default ActivityTableRow;
diff --git a/app/assets/javascripts/components/util/create_store.js b/app/assets/javascripts/components/util/create_store.js
index 95a5fbb3a2..98bbd57184 100644
--- a/app/assets/javascripts/components/util/create_store.js
+++ b/app/assets/javascripts/components/util/create_store.js
@@ -29,8 +29,8 @@ export const getStore = () => {
};
}
- // Determine if Redux Toolkit's safety checks should be enabled
- const enableReduxSafetyChecks = false;
+ // Determine if mutation checks should be enabled
+ const enableMutationChecks = false;
const store = configureStore({
reducer,
@@ -39,9 +39,9 @@ export const getStore = () => {
getDefaultMiddleware({
// Temporarily disable mutation checks feature to facilitate Redux Toolkit migration.
// TODO: Gradually resolve state mutations and re-enable these checks in the future.
- // Enable mutation checks when resolving or detecting these issues by setting enableReduxSafetyChecks to true.
- immutableCheck: enableReduxSafetyChecks,
- serializableCheck: enableReduxSafetyChecks,
+ // Enable mutation checks when resolving or detecting these issues by setting enableMutationChecks to true.
+ immutableCheck: enableMutationChecks,
+ serializableCheck: enableMutationChecks,
}),
});
diff --git a/app/assets/javascripts/reducers/uploads.js b/app/assets/javascripts/reducers/uploads.js
index f5c256969e..69e90ed293 100644
--- a/app/assets/javascripts/reducers/uploads.js
+++ b/app/assets/javascripts/reducers/uploads.js
@@ -30,7 +30,7 @@ const SORT_DESCENDING = {
export default function uploads(state = initialState, action) {
switch (action.type) {
case RECEIVE_UPLOADS: {
- const dataUploads = action.data?.course?.uploads || [];
+ const dataUploads = action.data.course.uploads;
// Intial sorting by upload date
const sortedModel = sortByKey(dataUploads, 'uploaded_at', state.sortKey, SORT_DESCENDING.uploaded_at);
diff --git a/app/assets/javascripts/surveys/modules/Survey.js b/app/assets/javascripts/surveys/modules/Survey.js
index 2aad692baa..dc8dab9f88 100644
--- a/app/assets/javascripts/surveys/modules/Survey.js
+++ b/app/assets/javascripts/surveys/modules/Survey.js
@@ -698,45 +698,50 @@ const Survey = {
}
},
- // To remove a conditional question from the flow if the condition that it depends on has changed.
- resetConditionalGroupChildren(conditionalGroup) {
- const { children, currentAnswers } = conditionalGroup;
-
- if ((typeof currentAnswers !== 'undefined' && currentAnswers !== null) && currentAnswers.length) {
- const excludeFromReset = [];
- currentAnswers.forEach((a) => { excludeFromReset.push(a); });
- children.forEach((question) => {
- const $question = $(question);
- let string;
- if ($question.data('conditional-question')) {
- string = $question.data('conditional-question');
- } else {
- string = $question.find('[data-conditional-question]').data('conditional-question');
- }
- const { value } = Utils.parseConditionalString(string);
- if (excludeFromReset.indexOf(value) === -1) {
- this.resetConditionalQuestion($question);
- } else {
- $question.removeClass('hidden');
- }
- });
- } else {
- children.forEach((question) => {
- this.resetConditionalQuestion($(question));
- if ($(question).hasClass('survey__question-row')) {
- const $parentBlock = $(question).parents(BLOCK_CONTAINER_SELECTOR);
- const blockIndex = $(question).data('block-index');
- if (!($parentBlock.find('.survey__question-row:not([data-conditional-question])').length > 1)) {
- this.resetConditionalQuestion($parentBlock);
- if (this.detachedParentBlocks[blockIndex] === undefined) {
- this.detachedParentBlocks[blockIndex] = $parentBlock;
- this.removeSlide($parentBlock);
- $parentBlock.detach();
- }
- }
- }
- });
- }
+ // FIXME: This is supposed to remove a conditional question from
+ // the flow if the condition that it depends on has changed.
+ // However, when this happens it leaves the survey in a state
+ // with no visible questions and no way to proceed.
+ // Disabling this feature means that, once inserted, a conditional
+ // question will not be removed, but that's better than a broken survey.
+ resetConditionalGroupChildren(/* conditionalGroup */) {
+ // const { children, currentAnswers } = conditionalGroup;
+
+ // if ((typeof currentAnswers !== 'undefined' && currentAnswers !== null) && currentAnswers.length) {
+ // const excludeFromReset = [];
+ // currentAnswers.forEach((a) => { excludeFromReset.push(a); });
+ // children.forEach((question) => {
+ // const $question = $(question);
+ // let string;
+ // if ($question.data('conditional-question')) {
+ // string = $question.data('conditional-question');
+ // } else {
+ // string = $question.find('[data-conditional-question]').data('conditional-question');
+ // }
+ // const { value } = Utils.parseConditionalString(string);
+ // if (excludeFromReset.indexOf(value) === -1) {
+ // this.resetConditionalQuestion($question);
+ // } else {
+ // $question.removeClass('hidden');
+ // }
+ // });
+ // } else {
+ // children.forEach((question) => {
+ // this.resetConditionalQuestion($(question));
+ // if ($(question).hasClass('survey__question-row')) {
+ // const $parentBlock = $(question).parents(BLOCK_CONTAINER_SELECTOR);
+ // const blockIndex = $(question).data('block-index');
+ // if (!($parentBlock.find('.survey__question-row:not([data-conditional-question])').length > 1)) {
+ // this.resetConditionalQuestion($parentBlock);
+ // if (this.detachedParentBlocks[blockIndex] === undefined) {
+ // this.detachedParentBlocks[blockIndex] = $parentBlock;
+ // this.removeSlide($parentBlock);
+ // $parentBlock.detach();
+ // }
+ // }
+ // }
+ // });
+ // }
},
removeSlide($block) {
@@ -748,29 +753,7 @@ const Survey = {
if ($question.hasClass('survey__question-row')) {
$question.removeAttr('style').addClass('hidden not-seen disabled');
} else {
- // Find which question group/slider this question belongs to by climbing up the DOM tree
- const $grandParents = $question.parents('[data-question-group-blocks]');
-
- // Extract the group's index number so we know which specific slider we need to modify
- const questionGroupIndex = $grandParents.data('question-group-blocks');
-
- // Get the actual slider jQuery object that we need to remove the question from
- const $slider = this.groupSliders[questionGroupIndex];
-
- // Get the slide index before detaching
- const slideIndex = $question.data('slick-index');
-
- // Remove the slide from Slick first
- $slider?.slick('slickRemove', slideIndex);
-
- // Then detach the element
$question.detach();
-
- // Updates Progress Bar on Top Right of the UI.
- this.indexBlocks();
-
- // Update Slide Button to Determine whether to show Next or Submit button.
- this.updateButtonText();
}
$question.find('input[type=text], textarea').val('');
$question.find('input:checked').removeAttr('checked');
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index b7928298f0..b4f3b2d6e7 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -46,8 +46,8 @@ def update_wiki(wiki)
def add_category(params)
name = ArticleUtils.format_article_title(params[:name])
- @category = Category.get_or_create(wiki: @wiki, depth: params[:depth],
- name:, source: params[:source])
+ @category = Category.find_or_create_by(wiki: @wiki, depth: params[:depth],
+ name:, source: params[:source])
@course.categories << @category
end
end
diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb
index 39aa038719..65941982a6 100644
--- a/app/controllers/courses_controller.rb
+++ b/app/controllers/courses_controller.rb
@@ -421,12 +421,13 @@ def update_timeslice_duration
def update_last_reviewed
username = params.dig(:course, 'last_reviewed', 'username')
timestamp = params.dig(:course, 'last_reviewed', 'timestamp')
- return unless username && timestamp
- @course.flags['last_reviewed'] = {
- 'username' => username,
- 'timestamp' => timestamp
- }
- @course.save
+ if username && timestamp
+ @course.flags['last_reviewed'] = {
+ 'username' => username,
+ 'timestamp' => timestamp
+ }
+ @course.save
+ end
end
def handle_post_course_creation_updates
diff --git a/app/controllers/onboarding_controller.rb b/app/controllers/onboarding_controller.rb
index 245701903b..28f5680c8c 100644
--- a/app/controllers/onboarding_controller.rb
+++ b/app/controllers/onboarding_controller.rb
@@ -22,7 +22,6 @@ def onboard
permissions: @permissions,
onboarded: true)
update_real_names_on_courses if Features.wiki_ed?
- EnrollmentReminderEmailWorker.schedule_reminder(@user)
CheckWikiEmailWorker.check(user: @user)
head :no_content
end
diff --git a/app/controllers/requested_accounts_controller.rb b/app/controllers/requested_accounts_controller.rb
index a38740ac68..561651570b 100644
--- a/app/controllers/requested_accounts_controller.rb
+++ b/app/controllers/requested_accounts_controller.rb
@@ -130,9 +130,10 @@ def passcode_valid?
def handle_existing_request
existing_request = RequestedAccount.find_by(course: @course, username: params[:username])
- return unless existing_request
- existing_request.update(email: params[:email])
- render json: { message: existing_request.updated_email_message }
- yield
+ if existing_request
+ existing_request.update(email: params[:email])
+ render json: { message: existing_request.updated_email_message }
+ yield
+ end
end
end
diff --git a/app/helpers/article_helper.rb b/app/helpers/article_helper.rb
index 5c87ac617a..949989e311 100644
--- a/app/helpers/article_helper.rb
+++ b/app/helpers/article_helper.rb
@@ -35,17 +35,26 @@ def rating_priority(rating)
def rating_display(rating)
rating = default_class(rating)
return nil if rating.nil?
- return rating if %w[fa ga fl].include? rating
- return rating[0] # use the first letter of the rating as the abbreviated version
+ if %w[fa ga fl].include? rating
+ return rating
+ else
+ return rating[0] # use the first letter of the rating as the abbreviated version
+ end
end
def default_class(rating)
# Handles the different article classes and returns a known article class
- return rating if %w[fa fl a ga b c start stub list].include? rating
- return 'b' if rating.eql? 'bplus'
- return 'a' if rating.eql? 'a/ga'
- return 'list' if %w[al bl cl sl].include? rating
- return nil
+ if %w[fa fl a ga b c start stub list].include? rating
+ return rating
+ elsif rating.eql? 'bplus'
+ return 'b'
+ elsif rating.eql? 'a/ga'
+ return 'a'
+ elsif %w[al bl cl sl].include? rating
+ return 'list'
+ else
+ return nil
+ end
end
def view_count(first_revision, average_views)
diff --git a/app/helpers/uploads_helper.rb b/app/helpers/uploads_helper.rb
index 9e4fc8c8e4..281081c876 100644
--- a/app/helpers/uploads_helper.rb
+++ b/app/helpers/uploads_helper.rb
@@ -3,7 +3,7 @@
#= Helpers for course views
module UploadsHelper
def pretty_filename(upload)
- pretty = CGI.unescape(upload.file_name)
+ pretty = upload.file_name
pretty['File:'] = ''
pretty
end
diff --git a/app/mailers/enrollment_reminder_mailer.rb b/app/mailers/enrollment_reminder_mailer.rb
deleted file mode 100644
index 5e5d1504cd..0000000000
--- a/app/mailers/enrollment_reminder_mailer.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-class EnrollmentReminderMailer < ApplicationMailer
- def self.send_reminder(user)
- return unless Features.email? && Features.wiki_ed?
- return if user.email.nil?
- email(user).deliver_now
- end
-
- def email(user)
- @user = user
- mail(to: @user.email, subject: 'Next steps on Wiki Education Dashboard')
- end
-end
diff --git a/app/models/campaign.rb b/app/models/campaign.rb
index d7a6ef2a70..bd5d147c0d 100644
--- a/app/models/campaign.rb
+++ b/app/models/campaign.rb
@@ -171,9 +171,10 @@ def valid_start_and_end_dates?
def set_slug
campaign_slug = slug.presence || title
self.slug = campaign_slug.downcase
- return if /^[\p{L}0-9_]+$/.match?(campaign_slug.downcase)
- # Strip everything but unicode letters and digits, and convert spaces to underscores.
- self.slug = campaign_slug.downcase.gsub(/[^\p{L}0-9 ]/, '').tr(' ', '_')
+ unless /^[\p{L}0-9_]+$/.match?(campaign_slug.downcase)
+ # Strip everything but unicode letters and digits, and convert spaces to underscores.
+ self.slug = campaign_slug.downcase.gsub(/[^\p{L}0-9 ]/, '').tr(' ', '_')
+ end
end
def set_default_times
diff --git a/app/models/wiki_content/category.rb b/app/models/wiki_content/category.rb
index 4ab2bfb199..638f28cd60 100644
--- a/app/models/wiki_content/category.rb
+++ b/app/models/wiki_content/category.rb
@@ -38,22 +38,6 @@ class Category < ApplicationRecord
less_than_or_equal_to: 3
}
- def self.get_or_create(wiki:, name:, depth:, source:)
- if source == 'pileid'
- get_or_create_by_pileid(wiki:, name:, depth:, source:)
- else
- find_or_create_by(wiki:, name:, depth:, source:)
- end
- end
-
- def self.get_or_create_by_pileid(wiki:, name:, depth:, source:)
- # For pagepile records, the name should be unique. Depth
- # is not applicable, and wiki gets set via PagePileApi if it
- # doesn't match.
- record = find_by(source:, name:)
- return record || create(wiki:, name:, depth:, source:)
- end
-
def self.refresh_categories_for(course, update_service: nil)
# Updating categories only if they were last updated since
# more than a day, or those which are newly created
diff --git a/app/services/add_sandbox_template.rb b/app/services/add_sandbox_template.rb
index 7ee90a62ad..e1f181e37b 100644
--- a/app/services/add_sandbox_template.rb
+++ b/app/services/add_sandbox_template.rb
@@ -19,8 +19,13 @@ def initialize(home_wiki:, sandbox:, sandbox_template:, current_user:)
def add_template
# Never double-post the sandbox template
- return if sandbox_template_present?
- default_template_present? ? replace_default_with_sandbox_template : add_sandbox_template
+ if sandbox_template_present?
+ return
+ elsif default_template_present?
+ replace_default_with_sandbox_template
+ else
+ add_sandbox_template
+ end
end
def sandbox_template_present?
diff --git a/app/services/sandbox_url_updator.rb b/app/services/sandbox_url_updator.rb
index bc7dd4ac64..2921a5e883 100644
--- a/app/services/sandbox_url_updator.rb
+++ b/app/services/sandbox_url_updator.rb
@@ -32,11 +32,8 @@ def validate_new_url
raise InvalidUrlError, I18n.t('assignments.invalid_url', url: @new_url) unless new_url_match
# Handle mismatched wiki
new_language, new_project = new_url_match.captures
- wiki_matches = (existing_language == new_language && existing_project == new_project)
- handle_mismatched_wiki unless wiki_matches
- end
-
- def handle_mismatched_wiki
- raise MismatchedWikiError, I18n.t('assignments.mismatched_wiki', url: @new_url)
+ unless existing_language == new_language && existing_project == new_project
+ raise MismatchedWikiError, I18n.t('assignments.mismatched_wiki', url: @new_url)
+ end
end
end
diff --git a/app/views/enrollment_reminder_mailer/email.html.haml b/app/views/enrollment_reminder_mailer/email.html.haml
deleted file mode 100644
index 4e74f66e2c..0000000000
--- a/app/views/enrollment_reminder_mailer/email.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-%link{rel: 'stylesheet', href:'/mailer.css'}
-%table.row
- %tbody
- %tr
- %th
- %table
- %tr
- %td.main-content
- %p.paragraph
- = "Hello #{@user.username}!"
- %p.paragraph
- Welcome to
- %a.link{:href => "https://#{ENV['dashboard_url']}"}
- Wiki Education Dashboard!
- %p.paragraph
- You've signed in to the Dashboard using your Wikipedia account, but
- you haven't joined a course. If you are a student and you were given
- an enrollment link by your instructor, you can now use that link
- to add yourself to the course page.
- %p.paragraph
- If you're here for another reason, or you've run into technical difficulties,
- let us know by replying to this email.
- %p.paragraph
- Best regards,
- %br
- %em
- The Wiki Education team
\ No newline at end of file
diff --git a/app/views/layouts/surveys.html.haml b/app/views/layouts/surveys.html.haml
index 4ee74b12d5..1147ec2ad1 100644
--- a/app/views/layouts/surveys.html.haml
+++ b/app/views/layouts/surveys.html.haml
@@ -20,6 +20,6 @@
// survey.js sometimes interferes with the rendering of results.
= javascript_include_tag '/assets/javascripts/jquery.min.js'
= hot_javascript_tag("survey") unless page_class == 'surveys results'
- - if can_administer? && !params.key?("preview")
+ - if can_administer?
= hot_javascript_tag("survey_admin")
= content_for :additional_javascripts
diff --git a/app/workers/enrollment_reminder_email_worker.rb b/app/workers/enrollment_reminder_email_worker.rb
deleted file mode 100644
index cee1b824b2..0000000000
--- a/app/workers/enrollment_reminder_email_worker.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-class EnrollmentReminderEmailWorker
- include Sidekiq::Worker
- sidekiq_options lock: :until_executed,
- retry: 0 # Move job to the 'dead' queue if it fails
-
- def self.schedule_reminder(user)
- # We only use this for users who didn't indicate they are instructors,
- # as it is intended to prompt students to use the enrollment link
- # in case something in the enrollment flow went wrong.
- return unless user.permissions == User::Permissions::NONE
- # Also make sure we don't spam people who revisit the onboarding flow.
- return if user.created_at < 1.day.ago
-
- perform_at(5.minutes.from_now, user.id)
- end
-
- def perform(user_id)
- user = User.find user_id
- return if user.courses.any?
-
- EnrollmentReminderMailer.send_reminder(user)
- end
-end
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index ace62afa0f..b4a893c305 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -1790,7 +1790,7 @@ fr:
user_training_status: État de la formation
user_no_training_status: Cet utilisateur n’a terminé aucun des modules de formation.
up_to_date_with_training: formation à jour
- uploads_doc: Nombre de fichiers téléversés sur Wikimedia Commons
+ uploads_doc: Nombre de fichiers téléversés sur Wikimédia Commons
ungreeted: Pas le bienvenu
username: Nom d’utilisateur
username_placeholder: Nom d’utilisateur
diff --git a/config/locales/lb.yml b/config/locales/lb.yml
index b4a3e0da28..5e78823525 100644
--- a/config/locales/lb.yml
+++ b/config/locales/lb.yml
@@ -462,7 +462,7 @@ lb:
next_update: Nächst erwaart Aktualiséierung
activity: Rezent Aktivitéit
articles_edited: Geännert Artikelen
- bytes_added: Byte derbäigesat
+ bytes_added: Byten derbäigesat
characters: Buschtawen
char_changed: Geännert Buschtawen
close_modal: Zoumaachen
diff --git a/config/locales/pa.yml b/config/locales/pa.yml
index 0b17dfa9d5..fc8e8430a1 100644
--- a/config/locales/pa.yml
+++ b/config/locales/pa.yml
@@ -44,7 +44,7 @@ pa:
sign_up_wikipedia: ਜਾਂ ਵਿਕੀਪੀਡੀਆ 'ਤੇ ਸਾਈਨ ਅੱਪ ਕਰੋ
sign_up_log_in_extended: (ਵਿਕੀਪੀਡੀਆ ਨਾਲ)
sign_up: ਖਾਤਾ ਬਣਾਓ
- submit: ਹਵਾਲੇ ਕਰੋ
+ submit: ਸਪੁਰਦ ਕਰੋ
training: ਸਿਖਲਾਈ
change: ਬਦਲੋ
details: ਵੇਰਵੇ
@@ -250,7 +250,7 @@ pa:
show_options: ਵਿਕਲਪ ਦਿਖਾਓ
subheading_message: ਆਓ ਕੰਮ ਕਰਨ ਲਈ ਇੱਕ ਵਿਕੀਪੀਡੀਆ ਲੇਖ ਲੱਭੀਏ।
subheading_message_wikidata: ਆਓ ਕੰਮ ਕਰਨ ਲਈ ਇੱਕ ਵਿਕੀਡਾਟਾ ਆਈਟਮ ਲੱਭੀਏ।
- submit: ਹਵਾਲੇ ਕਰੋ
+ submit: ਸਪੁਰਦ ਕਰੋ
tools: ਸੰਦ
training_status:
completed: ਪੂਰਾ ਹੋਇਆ
diff --git a/config/locales/se.yml b/config/locales/se.yml
index 603720a472..abbcbada41 100644
--- a/config/locales/se.yml
+++ b/config/locales/se.yml
@@ -62,7 +62,6 @@ se:
loading: Viežžamin bargguid...
remove: Sihko
select: Vállje
- submit: Sádde
campaign:
alert_label: Kampánjja várrehusat
alert_user_id: Geavaheaddji
@@ -105,7 +104,6 @@ se:
creator:
save_cloned_course: Vurke
scoping_methods:
- categories: Kategoriijat
templates: Mállet
student_editors: Geavaheaddjit
students: Geavaheaddjit
@@ -126,7 +124,6 @@ se:
article: Artihkal
file: Fiila
page: Siidu
- book: Girji
recent_activity:
file_name: Fiilanamma
revisions:
@@ -160,7 +157,6 @@ se:
update_username:
label: Ođđa geavaheaddjinamma
uploads:
- categories: Kategoriijat
file_name: Fiilanamma
license: 'Liseansa:'
users:
@@ -182,6 +178,4 @@ se:
training_status:
continue: Joatkke
view: Čájet
- notes:
- save_note: Ruõkk
...
diff --git a/config/locales/smn.yml b/config/locales/smn.yml
index 302e410736..4a63584423 100644
--- a/config/locales/smn.yml
+++ b/config/locales/smn.yml
@@ -64,7 +64,6 @@ smn:
article_link: Artikkâl
add_available_submit: Lasseet artikkâlijd
remove: Siho
- submit: Vuolgât
campaign:
alert_user_id: Kevttee
description: Kuvvim
@@ -95,7 +94,6 @@ smn:
creator:
save_cloned_course: Vuorkkii
scoping_methods:
- categories: Luokah
templates: Myenstereh
editable:
cancel: Jooskâ
@@ -108,7 +106,6 @@ smn:
upload_count: Vuorkkiimeh Commonsin
removed: Sikkum
namespace:
- book: Kirje
lexeme: Lekseem
recent_activity:
image: Kove
@@ -137,7 +134,6 @@ smn:
update_username:
label: Uđđâ kevtteenommâ
uploads:
- categories: Luokah
image: Kove
license: 'Liiseens:'
users:
diff --git a/config/locales/sms.yml b/config/locales/sms.yml
index aa6984052d..43e2314004 100644
--- a/config/locales/sms.yml
+++ b/config/locales/sms.yml
@@ -55,10 +55,8 @@ sms:
article_link: Artikkel
add_available_submit: Lââʹzzet artikkeeʹlid
bibliography: Bibliografia
- confirm_add_available: Haaʹlääk-a ton tuõđi artikkeeʹl %{title} lââʹzzted?
remove: Jaukkâd
select: Vaʹlljed
- submit: Vuõlttâd
campaign:
alert_article: Artikkeeʹl nõmm
alert_user_id: Õõʹnni
@@ -78,9 +76,9 @@ sms:
add_category: Lââʹzzet kategoria
add_psid: Lââʹzzet PetScan PSID
add_template: Lââʹzzet maall
- add_this_category: Lââʹzzet kategoriaid
+ add_this_category: Lââʹzzet tän kategoria
add_this_psid: Lââʹzzet tän PSID
- add_this_template: Lââʹzzet maallid
+ add_this_template: Lââʹzzet tän maall
articles_count: Artikkelmieʹrr
name: Nõmm
category_name: Kategorianõmm
@@ -145,7 +143,6 @@ sms:
find: Ooʒʒ prograamm
save_cloned_course: Ruõkk
scoping_methods:
- categories: Kategoria
templates: Maall
delete_course: Jaukkâd prograamm
explore: Ooʒʒ prograammi
@@ -170,7 +167,6 @@ sms:
project: Projeʹktt
file: Teâttõs
category: Kategoria
- book: Ǩeʹrjj
recent_activity:
article_title: Nõmm
image: Kaart da snimldõõǥǥ
@@ -210,7 +206,6 @@ sms:
update_username:
label: Ođđ õõʹnninõmm
uploads:
- categories: Kategoria
file_name: Teâttõsnõmm
image: Kartt leʼbe snimldõk
label: Ruõkkmõõžž
@@ -221,7 +216,6 @@ sms:
contribution_statistics: Muʹttemstatistiikk
course_passcode: 'Peittsääʹnn:'
edits: muttâz
- enroll_confirmation: Haaʹlääk-a ton tuõđi õõʹnni %{username} lââʹzzted?
first_name: Risttnõmm
last_name: Sokknõmm
name: Nõmm
@@ -231,7 +225,6 @@ sms:
one: '%{count} artikkel'
other: '%{count} artikkeeʹl'
references_count: Lââʹzztum teâttkääiv
- remove_confirmation: Haaʹlääk-a ton tuõđi õõʹnni %{username} jaukkeed?
training_module_status: Status
username: Õõʹnninõmm
username_placeholder: Õõʹnninõmm
diff --git a/docs/docker.md b/docs/docker.md
index 67ffabc9fa..1721bcb48c 100644
--- a/docs/docker.md
+++ b/docs/docker.md
@@ -58,10 +58,3 @@ If you are using Linux you may encounter permission error when trying to change
$ sudo chown $USER:$GROUPS -R ./
```
The command will change the owner of all file inside project directory to the `$USER:$GROUPS`
-
-If you encounter the following error when connecting to the database: _"There is an issue connecting with your hostname mysql"_, please run the following command:
-```sh
-bash ./update_hosts.sh
-```
-This script automatically resolves the issue by ensuring proper network mapping in your system's ```/etc/hosts``` file, facilitating seamless database connections. It retrieves the IP address of the MySQL Docker container, checks for any existing hostname entries, and updates or removes outdated ones. This process ensures that the hostname ```mysql``` is correctly mapped to the container’s IP address.
-This bash script is designed for Linux distributions like Fedora.
diff --git a/docs/setup.md b/docs/setup.md
index eed8430d67..5d10527a56 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -24,7 +24,7 @@ For Windows, the easiest way to get an environment set up is to [use Ubuntu with
## Prerequisite
There are some basic requirements for the script to work:
- git (to clone the repository)
-- node version 14 or newer
+- node version 14 or 16
- python 3.5 or newer
- ruby 3.1.2
- apt (debian) or homebrew (MacOS)
diff --git a/docs/training_module_scripts/training_content_to_docx.rb b/docs/training_module_scripts/training_content_to_docx.rb
deleted file mode 100644
index 532391cf5f..0000000000
--- a/docs/training_module_scripts/training_content_to_docx.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-
- # Script to create a Google Doc for reviewing and preparing edits for training modules.
-
- def html_from_slide(slide)
- "\n#{slide.title}
" <<
- PandocRuby.convert(slide.content, from: :markdown_github, to: :html) <<
- "
"
- end
-
- output = ''
-
-TrainingModule.all.each do |tm|
- output += "--- Training Module ##{tm.id}: #{tm.name}
---"
- output += "\n#{tm.description}
"
- tm.slides.each do |slide|
- output += html_from_slide(slide)
- end
-end
-
-File.open('training_content.html', 'wb') { |file| file.write(output) }
-
-# The pandoc conversion fails if a local asset file from the html does not exist.
-# As of January 2025, the only case of this is the inline icon in slide 334-authorship-highlighting.yml
-# Edit the html file to turn it into an absolute path — https://dashboard.wikiedu.org/assets/images/article-viewer.svg
-# then proceed with the conversion.
-
-`pandoc training_content.html --to docx --output trainings.docx`
\ No newline at end of file
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index e333688461..05b3201769 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -43,7 +43,7 @@ Your system has cmdtest installed, which provides a different program as yarn. U
- **To check if redis is running as a daemon in Linux** `ps aux | grep redis-server`
-- Use node v14 or newer to avoid any errors.
+- Use node v10 or lower to avoid any errors.
- **For WSL users , if rspec tests are taking too long to run** make sure to fork the repo in the linux file system and not in the windows partition. Use command `pwd` to know the exact path of your repo. If the path starts with `/mnt/`, the repo is in windows partition. Follow the documentation available at link: https://learn.microsoft.com/en-us/windows/wsl/filesystems to know more about storing and moving files in the linux file system.
If you have received error related to dependencies(`sudo apt-get install -y redis-server mariadb-server libmariadb-dev rvm nodejs npm pandoc`), try to install packages one by one and figure out which packages are creating problems.
diff --git a/lib/alerts/continued_course_activity_alert_manager.rb b/lib/alerts/continued_course_activity_alert_manager.rb
index df868b4480..2a8e501f88 100644
--- a/lib/alerts/continued_course_activity_alert_manager.rb
+++ b/lib/alerts/continued_course_activity_alert_manager.rb
@@ -22,7 +22,7 @@ def create_alerts
private
- MINIMUM_CHARACTERS_ADDED_AFTER_COURSE_END = 200
+ MINIMUM_CHARACTERS_ADDED_AFTER_COURSE_END = 1000
def significant_activity_after_course_end?(course)
user_ids = course.students.pluck(:id)
post_course_characters = Revision
diff --git a/lib/assignment_manager.rb b/lib/assignment_manager.rb
index d1bd7af70f..313cec6422 100644
--- a/lib/assignment_manager.rb
+++ b/lib/assignment_manager.rb
@@ -107,12 +107,10 @@ def set_article_from_database
def check_wiki_edu_discouraged_article
category = Category.find_by(name: ENV['blocked_assignment_category'])
- article_discouraged = (category.present? && category.article_titles.include?(@clean_title))
- handle_discouraged_article if article_discouraged
- end
- def handle_discouraged_article
- raise DiscouragedArticleError, I18n.t('assignments.blocked_assignment', title: @clean_title)
+ if category.present? && category.article_titles.include?(@clean_title)
+ raise DiscouragedArticleError, I18n.t('assignments.blocked_assignment', title: @clean_title)
+ end
end
def import_article_from_wiki
diff --git a/lib/importers/user_importer.rb b/lib/importers/user_importer.rb
index cbeab717e8..c1feebbc5a 100644
--- a/lib/importers/user_importer.rb
+++ b/lib/importers/user_importer.rb
@@ -114,7 +114,7 @@ def self.get_global_id(username)
def self.update_user_from_wiki(user, wiki)
user_data = WikiApi.new(wiki).get_user_info(user.username)
- return if user_data.nil? || user_data['missing']
+ return if user_data['missing']
user.update!(username: user_data['name'],
registered_at: user_data['registration'],
global_id: user_data&.dig('centralids', 'CentralAuth'))
diff --git a/lib/lift_wing_api.rb b/lib/lift_wing_api.rb
index 47ce0ccd53..7e3b949b2b 100644
--- a/lib/lift_wing_api.rb
+++ b/lib/lift_wing_api.rb
@@ -20,8 +20,6 @@ class LiftWingApi
# All the wikis with an articlequality model as of 2023-06-28
# https://wikitech.wikimedia.org/wiki/Machine_Learning/LiftWingq
AVAILABLE_WIKIPEDIAS = %w[en eu fa fr gl nl pt ru sv tr uk].freeze
- # config/initializers/retry_config.rb
- RETRY_COUNT = 5
def self.valid_wiki?(wiki)
return true if wiki.project == 'wikidata'
@@ -53,6 +51,7 @@ def get_revision_data(rev_ids)
end
log_error_batch(rev_ids)
+
return results
end
@@ -61,7 +60,7 @@ def get_revision_data(rev_ids)
# Returns a hash with wp10, features, deleted, and prediction, or empty hash if
# there is an error.
def get_single_revision_parsed_data(rev_id)
- tries ||= RETRY_COUNT
+ tries ||= 5
body = { rev_id:, extended_output: true }.to_json
response = lift_wing_server.post(quality_query_url, body)
parsed_response = Oj.load(response.body)
@@ -70,6 +69,7 @@ def get_single_revision_parsed_data(rev_id)
return { 'wp10' => nil, 'features' => nil, 'deleted' => deleted?(parsed_response),
'prediction' => nil }
end
+
build_successful_response(rev_id, parsed_response)
rescue StandardError => e
tries -= 1
diff --git a/lib/list_course_manager.rb b/lib/list_course_manager.rb
index 0208b9de11..e541424eac 100644
--- a/lib/list_course_manager.rb
+++ b/lib/list_course_manager.rb
@@ -39,11 +39,12 @@ def add_instructor_real_names
def add_classroom_program_manager_if_exists
cpm = SpecialUsers.classroom_program_manager
- return unless cpm && @course.type == 'ClassroomProgramCourse'
- CoursesUsers.create(user: cpm,
- course: @course,
- role: CoursesUsers::Roles::WIKI_ED_STAFF_ROLE,
- real_name: cpm.real_name)
+ if cpm && @course.type == 'ClassroomProgramCourse'
+ CoursesUsers.create(user: cpm,
+ course: @course,
+ role: CoursesUsers::Roles::WIKI_ED_STAFF_ROLE,
+ real_name: cpm.real_name)
+ end
end
def send_approval_notification_emails
diff --git a/lib/petscan_api.rb b/lib/petscan_api.rb
index cfe680e562..3dcddfb12d 100644
--- a/lib/petscan_api.rb
+++ b/lib/petscan_api.rb
@@ -8,6 +8,7 @@ class PetScanApi
def get_data(psid, update_service: nil)
url = query_url(psid)
response = petscan.get url
+ puts response.body
Oj.load(response.body)
rescue StandardError => e
log_error(e, update_service:,
diff --git a/lib/reference_counter_api.rb b/lib/reference_counter_api.rb
index b70d79e44d..cd51cb023b 100644
--- a/lib/reference_counter_api.rb
+++ b/lib/reference_counter_api.rb
@@ -56,12 +56,15 @@ def get_number_of_references_from_revision_id(rev_id)
tries ||= 5
response = toolforge_server.get(references_query_url(rev_id))
parsed_response = Oj.load(response.body)
- return { 'num_ref' => parsed_response['num_ref'] } if response.status == 200
- # Log the error and return empty hash
- # Sentry.capture_message 'Non-200 response hitting references counter API', level: 'warning',
- # extra: { project_code: @project_code, language_code: @language_code, rev_id:,
- # status_code: response.status, content: parsed_response }
- return { 'num_ref' => nil }
+ if response.status == 200
+ return { 'num_ref' => parsed_response['num_ref'] }
+ else
+ # Log the error and return empty hash
+ # Sentry.capture_message 'Non-200 response hitting references counter API', level: 'warning',
+ # extra: { project_code: @project_code, language_code: @language_code, rev_id:,
+ # status_code: response.status, content: parsed_response }
+ return { 'num_ref' => nil }
+ end
rescue StandardError => e
tries -= 1
retry unless tries.zero?
diff --git a/lib/revision_feedback_service.rb b/lib/revision_feedback_service.rb
index fbba09f3a9..356b426ee1 100644
--- a/lib/revision_feedback_service.rb
+++ b/lib/revision_feedback_service.rb
@@ -19,8 +19,9 @@ def feedback
def citation_feedback
ref_tags = @features['feature.wikitext.revision.ref_tags']
# cite_templates = @features['feature.enwiki.revision.cite_templates']
- return unless ref_tags < MINIMUM_REFERENCES
- @feedback << 'Cite your sources! This article needs more references.'
+ if ref_tags < MINIMUM_REFERENCES
+ @feedback << 'Cite your sources! This article needs more references.'
+ end
end
# The largest reasonable average section size, calculated from content characters
diff --git a/lib/revision_score_api_handler.rb b/lib/revision_score_api_handler.rb
index 8d9a8973d1..5cfc9c757f 100644
--- a/lib/revision_score_api_handler.rb
+++ b/lib/revision_score_api_handler.rb
@@ -14,8 +14,9 @@ def initialize(language: 'en', project: 'wikipedia', wiki: nil, update_service:
# Initialize LiftWingApi if the wiki is valid for it
@lift_wing_api = LiftWingApi.new(@wiki, @update_service) if LiftWingApi.valid_wiki?(@wiki)
# Initialize ReferenceCounterApi if the wiki is valid for it
- return unless ReferenceCounterApi.valid_wiki?(@wiki)
- @reference_counter_api = ReferenceCounterApi.new(@wiki, @update_service)
+ if ReferenceCounterApi.valid_wiki?(@wiki)
+ @reference_counter_api = ReferenceCounterApi.new(@wiki, @update_service)
+ end
end
# Returns data from LiftWing API and/or reference-counter API.
diff --git a/lib/training_module_due_date_manager.rb b/lib/training_module_due_date_manager.rb
index 178878a316..cba43334dc 100644
--- a/lib/training_module_due_date_manager.rb
+++ b/lib/training_module_due_date_manager.rb
@@ -49,8 +49,11 @@ def module_progress
def flags(course_id)
flags_hash = @tmu&.flags
- return flags_hash[course_id] || flags_hash if @training_module.exercise? && flags_hash.present?
- return flags_hash
+ if @training_module.exercise? && flags_hash.present?
+ return flags_hash[course_id] || flags_hash
+ else
+ return flags_hash
+ end
end
def sandbox_url
diff --git a/lib/wiki_api.rb b/lib/wiki_api.rb
index c189327842..65ddce0a7c 100644
--- a/lib/wiki_api.rb
+++ b/lib/wiki_api.rb
@@ -47,7 +47,7 @@ def get_user_info(username)
ususers: username,
usprop: 'centralids|registration' }
user_data = mediawiki('query', user_query)
- return unless user_data&.data&.dig('users')&.any?
+ return unless user_data.data['users'].any?
user_data.data['users'][0]
end
diff --git a/spec/features/multiwiki_assignment_spec.rb b/spec/features/multiwiki_assignment_spec.rb
index ee1a6e99a5..d119d009d4 100644
--- a/spec/features/multiwiki_assignment_spec.rb
+++ b/spec/features/multiwiki_assignment_spec.rb
@@ -58,8 +58,7 @@
within('#users') do
first('textarea').set(
- "Terre\nhttps://fr.wikipedia.org/wiki/Anglais",
- rapid: false
+ "Terre\nhttps://fr.wikipedia.org/wiki/Anglais"
)
end
click_button 'Assign all'
@@ -87,7 +86,7 @@
button.click
within('#users') do
- find('textarea', visible: true).set('No le des prisa, dolor', rapid: false)
+ find('textarea', visible: true).set('No le des prisa, dolor')
click_link 'Change'
find('div.wiki-select').click
within('.wiki-select') do
@@ -117,7 +116,7 @@
expect(button).to have_content 'Assign/remove an article'
button.click
within('#users') do
- first('textarea').set('https://wikisource.org/wiki/Heyder_Cansa', rapid: false)
+ first('textarea').set('https://wikisource.org/wiki/Heyder_Cansa')
end
click_button 'Assign'
visit "/courses/#{course.slug}/students/articles"
@@ -140,8 +139,7 @@
expect(button).to have_content 'Assign/remove an article'
button.click
within('#users') do
- first('textarea').set('https://incubator.wikimedia.org/wiki/Wp/kiu/Heyder_Cansa',
- rapid: false)
+ first('textarea').set('https://incubator.wikimedia.org/wiki/Wp/kiu/Heyder_Cansa')
end
click_button 'Assign'
visit "/courses/#{course.slug}/students/articles"
diff --git a/spec/features/survey_bugs_spec.rb b/spec/features/survey_bugs_spec.rb
index 9e0b02299a..e367f88cd7 100644
--- a/spec/features/survey_bugs_spec.rb
+++ b/spec/features/survey_bugs_spec.rb
@@ -128,8 +128,14 @@
click_button('Next', visible: true) # Q2
end
- sleep 2
- expect(page).to have_content('Submit Survey')
+ # Now this question ideally should be skipped
+ # but the code that did that breaks the survey
+ # by removing the question without sliding
+ # the next one into view.
+ find('.label', text: 'Maybe').click
+ within('div[data-progress-index="4"]') do
+ click_button('Next', visible: true) # Q3
+ end
# Now we can actually submit the survey
# and finish.
diff --git a/spec/helpers/uploads_helper_spec.rb b/spec/helpers/uploads_helper_spec.rb
index d03f4c2c99..91ae000087 100644
--- a/spec/helpers/uploads_helper_spec.rb
+++ b/spec/helpers/uploads_helper_spec.rb
@@ -10,14 +10,5 @@
result = pretty_filename(upload)
expect(result).to eq('My file.jpg')
end
-
- it 'formats a complex filename with encoded characters nicely for display' do
- upload = build(:commons_upload,
- # rubocop:disable Layout/LineLength
- file_name: 'File%3AA+sunflower+%F0%9F%8C%BB%F0%9F%8C%BB+in+Kaduna+Polytechnic%2CSabo+Campus.jpg')
- # rubocop:enable Layout/LineLength
- result = pretty_filename(upload)
- expect(result).to eq('A sunflower 🌻🌻 in Kaduna Polytechnic,Sabo Campus.jpg')
- end
end
end
diff --git a/spec/lib/importers/user_importer_spec.rb b/spec/lib/importers/user_importer_spec.rb
index 83ece5ff30..fdb4d596d7 100644
--- a/spec/lib/importers/user_importer_spec.rb
+++ b/spec/lib/importers/user_importer_spec.rb
@@ -180,16 +180,6 @@
describe '.update_user_from_wiki' do
let(:course) { create(:course) }
- let(:enwiki) { Wiki.get_or_create(language: 'en', project: 'wikipedia') }
-
- context 'when get_user_info returns nil' do
- it 'returns early without raising an error and does not update the user' do
- user = create(:user, username: 'RageSoss')
- allow_any_instance_of(WikiApi).to receive(:get_user_info).and_return(nil)
- expect(user).not_to receive(:update!)
- expect { described_class.update_user_from_wiki(user, enwiki) }.not_to raise_error
- end
- end
it 'cleans up records when there are collisions' do
VCR.use_cassette 'user/new_from_renamed_user' do
@@ -207,6 +197,7 @@
it 'sets the registration date from English Wikipedia' do
VCR.use_cassette 'user/enwiki_only_account' do
user = create(:user, username: 'Brady2421')
+ enwiki = Wiki.get_or_create(language: 'en', project: 'wikipedia')
described_class.update_user_from_wiki(user, enwiki)
expect(user.registered_at).not_to be_nil
end
diff --git a/spec/lib/lift_wing_api_spec.rb b/spec/lib/lift_wing_api_spec.rb
index 737483e39b..487fbc9f29 100644
--- a/spec/lib/lift_wing_api_spec.rb
+++ b/spec/lib/lift_wing_api_spec.rb
@@ -25,6 +25,9 @@
# Get revision data for valid rev ids for English Wikipedia
let(:subject0) { lift_wing_api_class_en_wiki.get_revision_data(rev_ids) }
+ # Get revision data for valid rev ids for Wikidata
+ let(:subject1) { described_class.new(wiki).get_revision_data(rev_ids) }
+
# Get revision data for deleted rev ids for English Wikipedia
let(:subject2) { lift_wing_api_class_en_wiki.get_revision_data([deleted_rev_id]) }
@@ -44,16 +47,8 @@
end
end
- context 'fetch json data from api.wikimedia.org' do
- before do
- stub_wiki_validation
- stub_lift_wing_response
- end
-
- # Get revision data for valid rev ids for Wikidata
- let(:subject1) { described_class.new(wiki).get_revision_data([829840084, 829840085]) }
-
- it 'fetches data for wikidata' do
+ it 'fetches json from api.wikimedia.org for wikidata' do
+ VCR.use_cassette 'liftwing_api/wikidata' do
expect(subject1).to be_a(Hash)
expect(subject1.dig('829840084')).to have_key('wp10')
expect(subject1.dig('829840084', 'wp10')).to eq(nil)
diff --git a/spec/lib/reference_counter_api_spec.rb b/spec/lib/reference_counter_api_spec.rb
index 69d3ee1784..0b4cd20e7c 100644
--- a/spec/lib/reference_counter_api_spec.rb
+++ b/spec/lib/reference_counter_api_spec.rb
@@ -11,7 +11,6 @@
let(:wikidata) { Wiki.get_or_create(language: nil, project: 'wikidata') }
let(:deleted_rev_ids) { [708326238] }
let(:rev_ids) { [5006940, 5006942, 5006946] }
- let(:response) { stub_reference_counter_response }
it 'raises InvalidProjectError if using wikidata project' do
expect do
@@ -19,20 +18,12 @@
end.to raise_error(described_class::InvalidProjectError)
end
- context 'returns the number of references' do
- before do
- stub_wiki_validation
- stub_reference_counter_response
- end
-
- # Get revision data for valid rev ids for Wikidata
- it 'if response is 200 OK', vcr: true do
- ref_counter_api = described_class.new(es_wiktionary)
- response = ref_counter_api.get_number_of_references_from_revision_ids rev_ids
- expect(response.dig('5006940', 'num_ref')).to eq(10)
- expect(response.dig('5006942', 'num_ref')).to eq(4)
- expect(response.dig('5006946', 'num_ref')).to eq(2)
- end
+ it 'returns the number of references if response is 200 OK', vcr: true do
+ ref_counter_api = described_class.new(es_wiktionary)
+ response = ref_counter_api.get_number_of_references_from_revision_ids rev_ids
+ expect(response.dig('5006940', 'num_ref')).to eq(10)
+ expect(response.dig('5006942', 'num_ref')).to eq(4)
+ expect(response.dig('5006946', 'num_ref')).to eq(2)
end
# it 'logs the message if response is not 200 OK', vcr: true do
diff --git a/spec/lib/revision_score_api_handler_spec.rb b/spec/lib/revision_score_api_handler_spec.rb
index c77c6461d6..2292f50f7b 100644
--- a/spec/lib/revision_score_api_handler_spec.rb
+++ b/spec/lib/revision_score_api_handler_spec.rb
@@ -9,7 +9,7 @@
let(:subject) { handler.get_revision_data [829840090, 829840091] }
describe '#get_revision_data' do
- it 'returns completed scores if data is retrieved without errors' do
+ it 'returns completed scores if retrieves data without errors' do
VCR.use_cassette 'revision_score_api_handler/en_wikipedia' do
expect(subject).to be_a(Hash)
expect(subject.dig('829840090', 'wp10').to_f).to be_within(0.01).of(62.81)
@@ -28,26 +28,15 @@
end
end
- describe 'error hitting LiftWingApi' do
- before do
- stub_wiki_validation
- stub_revision_score_reference_counter_reponse
- end
-
- let(:wiki) { create(:wiki, project: 'wikipedia', language: 'es') }
- let(:handler) { described_class.new(wiki:) }
- let(:subject) { handler.get_revision_data [829840090, 829840091] }
-
- it 'returns completed scores if there is an error hitting LiftWingApi' do
- VCR.use_cassette 'revision_score_api_handler/en_wikipedia_liftwing_error' do
- stub_request(:any, /.*api.wikimedia.org.*/)
- .to_raise(Errno::ETIMEDOUT)
- expect(subject).to be_a(Hash)
- expect(subject.dig('829840090')).to eq({ 'wp10' => nil,
- 'features' => { 'num_ref' => 132 }, 'deleted' => false, 'prediction' => nil })
- expect(subject.dig('829840091')).to eq({ 'wp10' => nil,
- 'features' => { 'num_ref' => 1 }, 'deleted' => false, 'prediction' => nil })
- end
+ it 'returns completed scores if there is an error hitting LiftWingApi' do
+ VCR.use_cassette 'revision_score_api_handler/en_wikipedia_liftwing_error' do
+ stub_request(:any, /.*api.wikimedia.org.*/)
+ .to_raise(Errno::ETIMEDOUT)
+ expect(subject).to be_a(Hash)
+ expect(subject.dig('829840090')).to eq({ 'wp10' => nil,
+ 'features' => { 'num_ref' => 132 }, 'deleted' => false, 'prediction' => nil })
+ expect(subject.dig('829840091')).to eq({ 'wp10' => nil,
+ 'features' => { 'num_ref' => 1 }, 'deleted' => false, 'prediction' => nil })
end
end
@@ -87,36 +76,34 @@
end
context 'when the wiki is available only for LiftWing API' do
+ before { stub_wiki_validation }
+
let(:wiki) { create(:wiki, project: 'wikidata', language: nil) }
let(:handler) { described_class.new(wiki:) }
-
- before do
- stub_wiki_validation
- stub_revision_score_lift_wing_reponse
- end
+ let(:subject) { handler.get_revision_data [144495297, 144495298] }
describe '#get_revision_data' do
- let(:subject) { handler.get_revision_data [144495297, 144495298] }
-
- it 'returns completed scores if data is retrieved without errors' do
- expect(subject).to be_a(Hash)
- expect(subject.dig('144495297', 'wp10').to_f).to eq(0)
- expect(subject.dig('144495297', 'features')).to be_a(Hash)
- expect(subject.dig('144495297', 'features',
- 'feature.len()')).to eq(2)
- # 'num_ref' key doesn't exist for wikidata features
- expect(subject.dig('144495297', 'features').key?('num_ref')).to eq(false)
- expect(subject.dig('144495297', 'deleted')).to eq(false)
- expect(subject.dig('144495297', 'prediction')).to eq('D')
-
- expect(subject.dig('144495298', 'wp10').to_f).to eq(0)
- expect(subject.dig('144495298', 'features')).to be_a(Hash)
- expect(subject.dig('144495298', 'features',
- 'feature.len()')).to eq(0)
- # 'num_ref' key doesn't exist for wikidata features
- expect(subject.dig('144495298', 'features').key?('num_ref')).to eq(false)
- expect(subject.dig('144495298', 'deleted')).to eq(false)
- expect(subject.dig('144495298', 'prediction')).to eq('E')
+ it 'returns completed scores if retrieves data without errors' do
+ VCR.use_cassette 'revision_score_api_handler/wikidata' do
+ expect(subject).to be_a(Hash)
+ expect(subject.dig('144495297', 'wp10').to_f).to eq(0)
+ expect(subject.dig('144495297', 'features')).to be_a(Hash)
+ expect(subject.dig('144495297', 'features',
+ 'feature.len()')).to eq(2)
+ # 'num_ref' key doesn't exist for wikidata features
+ expect(subject.dig('144495297', 'features').key?('num_ref')).to eq(false)
+ expect(subject.dig('144495297', 'deleted')).to eq(false)
+ expect(subject.dig('144495297', 'prediction')).to eq('D')
+
+ expect(subject.dig('144495298', 'wp10').to_f).to eq(0)
+ expect(subject.dig('144495298', 'features')).to be_a(Hash)
+ expect(subject.dig('144495298', 'features',
+ 'feature.len()')).to eq(0)
+ # 'num_ref' key doesn't exist for wikidata features
+ expect(subject.dig('144495298', 'features').key?('num_ref')).to eq(false)
+ expect(subject.dig('144495298', 'deleted')).to eq(false)
+ expect(subject.dig('144495298', 'prediction')).to eq('E')
+ end
end
it 'returns completed scores if there is an error hitting LiftWingApi' do
@@ -132,10 +119,7 @@
end
context 'when the wiki is available only for reference-counter API' do
- before do
- stub_wiki_validation
- stub_revision_score_reference_counter_reponse
- end
+ before { stub_wiki_validation }
let(:wiki) { create(:wiki, project: 'wikipedia', language: 'es') }
let(:handler) { described_class.new(wiki:) }
@@ -143,11 +127,13 @@
describe '#get_revision_data' do
it 'returns completed scores if retrieves data without errors' do
- expect(subject).to be_a(Hash)
- expect(subject.dig('157412237')).to eq({ 'wp10' => nil,
- 'features' => { 'num_ref' => 111 }, 'deleted' => false, 'prediction' => nil })
- expect(subject.dig('157417768')).to eq({ 'wp10' => nil,
- 'features' => { 'num_ref' => 42 }, 'deleted' => false, 'prediction' => nil })
+ VCR.use_cassette 'revision_score_api_handler/es_wikipedia' do
+ expect(subject).to be_a(Hash)
+ expect(subject.dig('157412237')).to eq({ 'wp10' => nil,
+ 'features' => { 'num_ref' => 111 }, 'deleted' => false, 'prediction' => nil })
+ expect(subject.dig('157417768')).to eq({ 'wp10' => nil,
+ 'features' => { 'num_ref' => 42 }, 'deleted' => false, 'prediction' => nil })
+ end
end
it 'returns completed scores if there is an error hitting reference-counter api' do
diff --git a/spec/lib/wiki_api_spec.rb b/spec/lib/wiki_api_spec.rb
index 601612cf97..73ac618f1a 100644
--- a/spec/lib/wiki_api_spec.rb
+++ b/spec/lib/wiki_api_spec.rb
@@ -169,18 +169,6 @@ class UnexpectedError < StandardError; end
end
end
- describe '#get_user_info' do
- let(:wiki) { Wiki.new(language: 'en', project: 'wikipedia') }
-
- context 'when mediawiki query returns nil' do
- it 'returns early without raising an error' do
- allow_any_instance_of(described_class).to receive(:mediawiki).and_return(nil)
- expect { described_class.new.get_user_info('Ragesoss') }.not_to raise_error
- expect(described_class.new.get_user_info('Ragesoss')).to be_nil
- end
- end
- end
-
describe '#redirect?' do
let(:wiki) { Wiki.new(language: 'en', project: 'wikipedia') }
let(:subject) { described_class.new(wiki).redirect?(title) }
diff --git a/spec/mailers/previews/enrollment_reminder_mailer_preview.rb b/spec/mailers/previews/enrollment_reminder_mailer_preview.rb
deleted file mode 100644
index 1df2e243aa..0000000000
--- a/spec/mailers/previews/enrollment_reminder_mailer_preview.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-class EnrollmentReminderMailerPreview < ActionMailer::Preview
- def enrollment_reminder_email
- EnrollmentReminderMailer.email(example_user)
- end
-
- private
-
- def example_user
- User.new(email: 'sage@example.com', username: 'Ragesoss')
- end
-end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 5db7a2d819..a89ea159a5 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -24,11 +24,6 @@
Rails.cache.clear
Capybara::Screenshot.prune_strategy = :keep_last_run
-Capybara::Screenshot.register_filename_prefix_formatter(:rspec) do |example|
- file_name = example.file_path.split('/').last.gsub('.rb', '')
- formatted_description = example.description.tr(' ', '-').gsub(%r{^.*/spec/}, '')
- "screenshot_#{file_name}_description_#{formatted_description}"
-end
Capybara.save_path = 'tmp/screenshots/'
Capybara.server = :puma, { Silent: true }
Capybara.default_max_wait_time = 10
@@ -103,6 +98,7 @@
# them.
# Instead, we clear and print any after-success error
# logs in the `before` block above.
+ Capybara::Screenshot.screenshot_and_save_page if example.exception
errors = page.driver.browser.logs.get(:browser)
# pass `js_error_expected: true` to skip JS error checking
diff --git a/spec/services/update_course_stats_spec.rb b/spec/services/update_course_stats_spec.rb
index aa09fd3d6c..361c74ecff 100644
--- a/spec/services/update_course_stats_spec.rb
+++ b/spec/services/update_course_stats_spec.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+
require 'rails_helper'
describe UpdateCourseStats do
@@ -80,18 +81,13 @@
it 'tracks update errors properly in Replica' do
allow(Sentry).to receive(:capture_exception)
- # Stub the constant RETRY_COUNT for this test to ensure retries are controlled
- stub_const('LiftWingApi::RETRY_COUNT', 1)
-
# Raising errors only in Replica
stub_request(:any, %r{https://replica-revision-tools.wmcloud.org/.*}).to_raise(Errno::ECONNREFUSED)
VCR.use_cassette 'course_update/replica' do
subject
end
sentry_tag_uuid = subject.sentry_tag_uuid
- expected_error_count = subject.error_count
-
- expect(course.flags['update_logs'][1]['error_count']).to eq expected_error_count
+ expect(course.flags['update_logs'][1]['error_count']).to eq 1
expect(course.flags['update_logs'][1]['sentry_tag_uuid']).to eq sentry_tag_uuid
# Checking whether Sentry receives correct error and tags as arguments
@@ -104,20 +100,18 @@
it 'tracks update errors properly in LiftWing' do
allow(Sentry).to receive(:capture_exception)
- stub_const('LiftWingApi::RETRY_COUNT', 1)
# Raising errors only in LiftWing
stub_request(:any, %r{https://api.wikimedia.org/service/lw.*}).to_raise(Faraday::ConnectionFailed)
VCR.use_cassette 'course_update/lift_wing_api' do
subject
end
sentry_tag_uuid = subject.sentry_tag_uuid
- expected_error_count = subject.error_count
- expect(course.flags['update_logs'][1]['error_count']).to eq expected_error_count
+ expect(course.flags['update_logs'][1]['error_count']).to eq 8
expect(course.flags['update_logs'][1]['sentry_tag_uuid']).to eq sentry_tag_uuid
# Checking whether Sentry receives correct error and tags as arguments
expect(Sentry).to have_received(:capture_exception)
- .exactly(expected_error_count).times.with(Faraday::ConnectionFailed, anything)
+ .exactly(8).times.with(Faraday::ConnectionFailed, anything)
expect(Sentry).to have_received(:capture_exception)
.exactly(8).times.with anything, hash_including(tags: { update_service_id: sentry_tag_uuid,
course: course.slug })
diff --git a/spec/support/request_helpers.rb b/spec/support/request_helpers.rb
index 56e9d54859..347b81e874 100644
--- a/spec/support/request_helpers.rb
+++ b/spec/support/request_helpers.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require 'json' # Ensure JSON is required for to_json
#= Stubs for various requests
module RequestHelpers
@@ -562,159 +561,4 @@ def stub_course
stub_timeline
stub_users
end
-
- def stub_lift_wing_response
- request_body = {
- 'wikidatawiki' => {
- 'models' => {
- 'itemquality' => {
- 'version' => '0.5.0'
- }
- },
- 'scores' => {
- '829840084' => {
- 'itemquality' => {
- 'score' => {
- 'prediction' => 'D',
- 'probability' => { 'A' => 0.001863543366261331, 'B' => 0.001863543366261331 }
- },
- 'features' => {
- 'feature.len()' => 3.0,
- 'feature.len()' => 3.0,
- 'feature.len()' => 0.0
- }
- }
- },
- '829840085' => {
- 'itemquality' => {
- 'score' => {
- 'prediction' => 'D',
- 'probability' => { 'A' => 0.005396336449201622, 'B' => 0.005396336449201622 }
- },
- 'features' => {
- 'feature.len()' => 10.0,
- 'feature.len()' => 9.0,
- 'feature.len()' => 1.0
- }
- }
- }
- }
- }
- }
- stub_request(:post, 'https://api.wikimedia.org/service/lw/inference/v1/models/wikidatawiki-itemquality:predict')
- .with(
- body: hash_including(extended_output: true),
- headers: { 'Content-Type': 'application/json' }
- ).to_return(
- status: 200,
- body: request_body.to_json
- )
- end
-
- def stub_reference_counter_response
- # Define the response body in a hash with revision IDs as keys
- request_body = {
- '5006940' => { 'num_ref' => 10, 'lang' => 'es', 'project' => 'wiktionary',
- 'revid' => 5006940 },
- '5006942' => { 'num_ref' => 4, 'lang' => 'es', 'project' => 'wiktionary',
- 'revid' => 5006942 },
- '5006946' => { 'num_ref' => 2, 'lang' => 'es', 'project' => 'wiktionary', 'revid' => 5006946 }
- }
-
- # Stub the request to match the revision ID in the URL
- stub_request(:get, %r{https://reference-counter.toolforge.org/api/v1/references/wiktionary/es/\d+})
- .to_return(
- status: 200,
- body: lambda do |request|
- # Extract revision ID from the URL
- rev_id = request.uri.path.split('/').last
- # Return the appropriate response based on the revision ID
- { 'num_ref' => request_body[rev_id.to_s]['num_ref'] }.to_json
- end,
- headers: { 'Content-Type' => 'application/json' }
- )
- end
-
- def stub_revision_score_lift_wing_reponse
- request_body =
- {
- 'wikidatawiki' => {
- 'models' => {
- 'itemquality' => {
- 'version' => '0.5.0'
- }
- },
- 'scores' => {
- '144495297' => {
- 'itemquality' => {
- 'score' => {
- 'prediction' => 'D',
- 'probability' => {
- 'A' => 0.004943068308984735,
- 'B' => 0.004943068308984735
- }
- },
- 'features' => {
- 'feature.len()' => 3.0,
- 'feature.len()' => 3.0,
- 'feature.len()' => 2.0,
- 'feature.len()' => 2.0
- }
- }
- },
- '144495298' => {
- 'itemquality' => {
- 'score' => {
- 'prediction' => 'E',
- 'probability' => {
- 'A' => 0.0006501008909422321,
- 'B' => 0.000887054617313177
- }
- },
- 'features' => {
- 'feature.len()' => 1.0,
- 'feature.len()' => 1.0,
- 'feature.len()' => 0.0,
- 'feature.len()' => 0.0
- }
- }
- }
- }
- }
- }
- stub_request(:post, 'https://api.wikimedia.org/service/lw/inference/v1/models/wikidatawiki-itemquality:predict')
- .with(
- body: hash_including(extended_output: true),
- headers: { 'Content-Type': 'application/json' }
- ).to_return(
- status: 200,
- body: request_body.to_json
- )
- end
-
- def stub_revision_score_reference_counter_reponse
- request_body = {
- '157412237' => { 'num_ref' => 111, 'lang' => 'es', 'project' => 'wikipedia',
- 'revid' => 157412237 },
- '157417768' => { 'num_ref' => 42, 'lang' => 'es', 'project' => 'wikipedia',
- 'revid' => 157417768 },
- '829840090' => { 'num_ref' => 132, 'lang' => 'es', 'project' => 'wikipedia',
- 'revid' => 829840090 },
- '829840091' => { 'num_ref' => 1, 'lang' => 'es', 'project' => 'wikipedia',
- 'revid' => 829840091 }
- }
-
- # Stub the request to match the revision ID in the URL
- stub_request(:get, %r{https://reference-counter.toolforge.org/api/v1/references/wikipedia/es/\d+})
- .to_return(
- status: 200,
- body: lambda do |request|
- # Extract revision ID from the URL
- rev_id = request.uri.path.split('/').last
- # Return the appropriate response based on the revision ID
- { 'num_ref' => request_body[rev_id.to_s]['num_ref'] }.to_json
- end,
- headers: { 'Content-Type' => 'application/json' }
- )
- end
end
diff --git a/training_content/wiki_ed/slides/60-keeping-track-of-your-work/6003-stage-1-bibliography.yml b/training_content/wiki_ed/slides/60-keeping-track-of-your-work/6003-stage-1-bibliography.yml
index 4bcc294ae9..d2a8633f9a 100644
--- a/training_content/wiki_ed/slides/60-keeping-track-of-your-work/6003-stage-1-bibliography.yml
+++ b/training_content/wiki_ed/slides/60-keeping-track-of-your-work/6003-stage-1-bibliography.yml
@@ -4,7 +4,7 @@ summary:
content: |
+
-
+ /dev/null
-
-# Print success message
-echo "MySQL IP address has been added to /etc/hosts."
\ No newline at end of file