diff --git a/app/assets/javascripts/components/article_finder/article_finder.jsx b/app/assets/javascripts/components/article_finder/article_finder.jsx
index 9c3476d060..dfc3af8777 100644
--- a/app/assets/javascripts/components/article_finder/article_finder.jsx
+++ b/app/assets/javascripts/components/article_finder/article_finder.jsx
@@ -1,468 +1,532 @@
-import React from 'react';
-import createReactClass from 'create-react-class';
-import { connect } from 'react-redux';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { useDispatch } from 'react-redux';
import InputRange from 'react-input-range';
import { includes, map, find } from 'lodash-es';
import qs from 'query-string';
import SelectedWikiOption from '../common/selected_wiki_option';
-import { compose } from 'redux';
-import withRouter from '../util/withRouter';
-
import TextInput from '../common/text_input.jsx';
import ArticleFinderRow from './article_finder_row.jsx';
import List from '../common/list.jsx';
import Loading from '../common/loading.jsx';
-
import { STUDENT_ROLE } from '../../constants';
-import { ORESSupportedWiki, PageAssessmentSupportedWiki } from '../../utils/article_finder_language_mappings.js';
-import { fetchCategoryResults, fetchKeywordResults, updateFields, sortArticleFinder, resetArticleFinder, clearResults } from '../../actions/article_finder_action.js';
-import { fetchAssignments, addAssignment, deleteAssignment } from '../../actions/assignment_actions.js';
-import { getFilteredArticleFinder } from '../../selectors';
-
+import {
+ ORESSupportedWiki,
+ PageAssessmentSupportedWiki,
+} from '../../utils/article_finder_language_mappings.js';
+import {
+ fetchCategoryResults,
+ fetchKeywordResults,
+ updateFields,
+ sortArticleFinder,
+ resetArticleFinder,
+ clearResults,
+} from '../../actions/article_finder_action.js';
+import {
+ fetchAssignments,
+ addAssignment,
+ deleteAssignment,
+} from '../../actions/assignment_actions.js';
import { trackedWikisMaker } from '../../utils/wiki_utils';
import ArticleUtils from '../../utils/article_utils';
-
-const ArticleFinder = createReactClass({
- getDefaultProps() {
- return {
- course: {
- home_wiki: {
- language: 'en',
- project: 'wikipedia'
- }
+import { useLocation } from 'react-router-dom';
+import { table_keys } from './constants';
+import useInitialiseArticleFinder from './hooks/useInitialiseArticleFinder';
+
+const ArticleFinder = (props) => {
+ const dispatch = useDispatch();
+ const location = useLocation();
+
+ const {
+ course_id,
+ current_user,
+ course = {
+ home_wiki: {
+ language: 'en',
+ project: 'wikipedia',
+ },
+ },
+ } = props;
+
+ const {
+ articles,
+ unfilteredArticles,
+ wikidataLabels,
+ loading,
+ search_term,
+ min_views,
+ article_quality,
+ search_type,
+ continue_results,
+ offset,
+ cmcontinue,
+ assignments,
+ loadingAssignments,
+ fetchState,
+ sort,
+ home_wiki,
+ selectedWiki,
+ buildURL,
+ } = useInitialiseArticleFinder();
+
+ const [isSubmitted, setIsSubmitted] = useState(false);
+ const [showFilters, setShowFilters] = useState(false);
+ const [_table_keys, setTableKeys] = useState(table_keys);
+ const searchBoxRef = useRef('');
+
+ useEffect(() => {
+ const loadOnMount = () => {
+ if (window.location.search.substring(1)) {
+ getParamsURL();
+ }
+ if (course_id && loadingAssignments) {
+ dispatch(fetchAssignments(course_id));
}
+ if (location.project) {
+ return _updateFields('wiki', {
+ language: location.language,
+ project: location.project,
+ });
+ }
+ return _updateFields('home_wiki', home_wiki);
};
- },
+ loadOnMount();
- getInitialState() {
- return {
- isSubmitted: false,
- showFilters: false,
+ return () => {
+ dispatch(clearResults());
+ dispatch(resetArticleFinder());
};
- },
+ }, []);
+
+ useEffect(() => {
+ const handleSortKey = () => {
+ const newTableKeys = { ..._table_keys };
+ if (sort.key) {
+ const order = sort.sortKey ? 'asc' : 'desc';
+ Object.entries(newTableKeys).forEach((item) => {
+ const [key, value] = item;
+ if (key === sort.key) {
+ value.order = order;
+ } else {
+ delete value.order;
+ }
+ });
+ console.log('newTableKeys', newTableKeys);
+ setTableKeys(newTableKeys);
+ }
+ if (
+ !includes(ORESSupportedWiki.languages, selectedWiki.language)
+ || !includes(ORESSupportedWiki.projects, selectedWiki.project)
+ ) {
+ delete newTableKeys.revScore;
+ setTableKeys(newTableKeys);
+ }
- componentDidMount() {
- if (window.location.search.substring(1)) {
- this.getParamsURL();
- }
- if (this.props.course_id && this.props.loadingAssignments) {
- this.props.fetchAssignments(this.props.course_id);
- }
- if (this.props.router.location.project) {
- return this.updateFields('wiki', { language: this.props.router.location.language, project: this.props.router.location.project });
- }
- return this.updateFields('home_wiki', this.props.course.home_wiki);
- },
+ if (
+ !PageAssessmentSupportedWiki[selectedWiki.project]
+ || !includes(
+ PageAssessmentSupportedWiki[selectedWiki.project],
+ selectedWiki.language
+ )
+ ) {
+ delete newTableKeys.grade;
+ setTableKeys(newTableKeys);
+ }
- componentWillUnmount() {
- return this.props.resetArticleFinder();
- },
+ if (!course_id || !current_user.id || current_user.notEnrolled) {
+ delete newTableKeys.tools;
+ setTableKeys(newTableKeys);
+ }
+ };
- onKeyDown(keyCode, ref) {
+ handleSortKey();
+ }, [
+ sort.key,
+ sort.sortKey,
+ course_id,
+ current_user.id,
+ current_user.notEnrolled,
+ PageAssessmentSupportedWiki[selectedWiki.project],
+ ORESSupportedWiki,
+ ]);
+
+ const onKeyDown = (keyCode, ref) => {
if (keyCode === 13) {
ref.blur();
- this.searchArticles();
+ searchArticles();
}
- },
+ };
- getParamsURL() {
+ const getParamsURL = () => {
const query = qs.parse(window.location.search);
const entries = Object.entries(query);
entries.map(([key, val]) => {
- val = (key === 'article_quality') ? parseInt(val) : val;
- return this.updateFields(key, val);
+ val = key === 'article_quality' ? parseInt(val) : val;
+ return _updateFields(key, val);
});
- },
-
- updateFields(key, value) {
- const update_field = this.props.updateFields(key, value);
- Promise.resolve(update_field).then(() => {
- if (this.props.search_term.length !== 0) {
- this.buildURL();
- }
- });
- },
-
- toggleFilter() {
- return this.setState({
- showFilters: !this.state.showFilters
- });
- },
- buildURL() {
- let queryStringUrl = window.location.href.split('?')[0];
- const params_array = ['search_type', 'article_quality', 'min_views'];
- queryStringUrl += `?search_term=${this.props.search_term}`;
- params_array.forEach((param) => {
- return queryStringUrl += `&${param}=${this.props[param]}`;
- });
- history.replaceState(window.location.href, 'query_string', queryStringUrl);
- },
- searchArticles() {
- this.setState({
- isSubmitted: true,
- });
- if (this.props.search_term === '') {
- return this.setState({
- isSubmitted: false,
- });
- } else if (this.props.search_type === 'keyword') {
- this.buildURL();
- return this.props.fetchKeywordResults(this.props.search_term, this.props.selectedWiki);
+ };
+
+ const _updateFields = (key, value) => {
+ dispatch(updateFields(key, value));
+ };
+
+ const toggleFilter = () => {
+ setShowFilters(isShown => !isShown);
+ };
+
+ const searchArticles = () => {
+ setIsSubmitted(true);
+ if (search_term === '') {
+ setIsSubmitted(false);
+ } else if (search_type === 'keyword') {
+ buildURL();
+ return dispatch(fetchKeywordResults(search_term, selectedWiki));
}
- return this.props.fetchCategoryResults(this.props.search_term, this.props.selectedWiki);
- },
+ return dispatch(fetchCategoryResults(search_term, selectedWiki));
+ };
- fetchMoreResults() {
- if (this.props.search_type === 'keyword') {
- return this.props.fetchKeywordResults(this.props.search_term, this.props.selectedWiki, this.props.offset, true);
+ const fetchMoreResults = () => {
+ if (search_type === 'keyword') {
+ return dispatch(
+ fetchKeywordResults(search_term, selectedWiki, offset, true)
+ );
}
- return this.props.fetchCategoryResults(this.props.search_term, this.props.selectedWiki, this.props.cmcontinue, true);
- },
-
- handleChange(e) {
- const grade = e.target.value;
- return this.props.updateFields('grade', grade);
- },
+ return dispatch(
+ fetchCategoryResults(search_term, selectedWiki, cmcontinue, true)
+ );
+ };
- handleWikiChange(wiki) {
+ const handleWikiChange = (wiki) => {
wiki = wiki.value;
- this.setState({ isSubmitted: false });
- this.props.clearResults();
- return this.updateFields('wiki', { language: wiki.language, project: wiki.project });
- },
-
- sortSelect(e) {
- this.props.sortArticleFinder(e.target.value);
- },
-
- render() {
- const searchButton = ;
- const searchTerm = (
- {searchButton}
- );
-
- const searchType = (
-
-
-
-
-
-
-
-
+ setIsSubmitted(false);
+ dispatch(clearResults());
+ return _updateFields('wiki', {
+ language: wiki.language,
+ project: wiki.project,
+ });
+ };
+
+ const searchTerm = (
+
+
+
+ );
+
+ const searchType = () => (
+
+ );
- const minimumViews = (
-
-
(
+
+
+
+ );
+
+ const articleQuality = () => (
+
+
+
+ _updateFields('article_quality', value)}
+ step={1}
/>
- );
-
- const articleQuality = (
-
-
-
- this.updateFields('article_quality', value)}
- step={1}
- />
-
-
- );
- let filters;
- if (this.state.showFilters) {
- filters = (
-
- {minimumViews}
- {articleQuality}
- {searchType}
-
- );
- }
-
- let filterButton;
- if (!this.state.showFilters) {
- filterButton = (
-
- );
- } else {
- filterButton = (
-
- );
+
+ );
+
+ const filterBtnClassAndText = useMemo(() => {
+ if (!showFilters) {
+ return {
+ className: 'button dark',
+ text: I18n.t('article_finder.show_options'),
+ };
}
+ return {
+ className: 'button',
+ text: I18n.t('article_finder.hide_options'),
+ };
+ }, [showFilters]);
- let filterBlock;
- if (this.state.isSubmitted && !this.props.loading) {
- filterBlock = (
+ const filterBlock = useMemo(() => {
+ if (isSubmitted && !loading) {
+ return (
- {filterButton}
-
-
- {filters}
+
+ {showFilters ? (
+
+
+ {minimumViews()}
+ {articleQuality()}
+ {searchType()}
+
+
+ ) : null}
);
}
-
- const keys = {
- relevanceIndex: {
- label: I18n.t('article_finder.relevanceIndex'),
- desktop_only: false
- },
- title: {
- label: I18n.t('articles.title'),
- desktop_only: false
- },
- grade: {
- label: I18n.t('article_finder.page_assessment_class'),
- desktop_only: false,
- sortable: true,
- },
- revScore: {
- label: I18n.t('article_finder.completeness_estimate'),
- desktop_only: false,
- sortable: true,
- },
- pageviews: {
- label: I18n.t('article_finder.average_views'),
- desktop_only: false,
- sortable: true,
- },
- tools: {
- label: I18n.t('article_finder.tools'),
- desktop_only: false,
- sortable: false,
+ }, [
+ isSubmitted,
+ loading,
+ showFilters,
+ minimumViews,
+ articleQuality,
+ searchType,
+ ]);
+
+ const _sortArticleFinder = (key) => {
+ dispatch(sortArticleFinder(key));
+ };
+
+ const renderList = () => {
+ const modifiedAssignmentsArray = map(assignments, (element) => {
+ if (!element.language && !element.project) {
+ return {
+ ...element,
+ language: selectedWiki.language,
+ project: selectedWiki.project,
+ };
}
- };
- if (this.props.sort.key) {
- const order = (this.props.sort.sortKey) ? 'asc' : 'desc';
- keys[this.props.sort.key].order = order;
- }
- if (!includes(ORESSupportedWiki.languages, this.props.selectedWiki.language) || !includes(ORESSupportedWiki.projects, this.props.selectedWiki.project)) {
- delete keys.revScore;
- }
-
- if (!PageAssessmentSupportedWiki[this.props.selectedWiki.project] || !includes(PageAssessmentSupportedWiki[this.props.selectedWiki.project], this.props.selectedWiki.language)) {
- delete keys.grade;
- }
-
- if (!this.props.course_id || !this.props.current_user.id || this.props.current_user.notEnrolled) {
- delete keys.tools;
- }
+ return element;
+ });
- let list;
- if (this.state.isSubmitted && !this.props.loading) {
- const modifiedAssignmentsArray = map(this.props.assignments, (element) => {
- if (!element.language && !element.project) {
- return {
- ...element,
- language: this.props.selectedWiki.language,
- project: this.props.selectedWiki.project
- };
- }
- return element;
- });
-
- const elements = map(this.props.articles, (article, title) => {
- let assignment;
- if (this.props.course_id) {
- if (this.props.current_user.isAdvancedRole) {
- assignment = find(modifiedAssignmentsArray, { article_title: title, user_id: null, language: this.props.selectedWiki.language, project: this.props.selectedWiki.project });
- } else if (this.props.current_user.role === STUDENT_ROLE) {
- assignment = find(modifiedAssignmentsArray, { article_title: title, user_id: this.props.current_user.id, language: this.props.selectedWiki.language, project: this.props.selectedWiki.project });
- }
+ const elements = map(articles, (article, title) => {
+ let assignment;
+ if (course_id) {
+ if (current_user.isAdvancedRole) {
+ assignment = find(modifiedAssignmentsArray, {
+ article_title: title,
+ user_id: null,
+ language: selectedWiki.language,
+ project: selectedWiki.project,
+ });
+ } else if (current_user.role === STUDENT_ROLE) {
+ assignment = find(modifiedAssignmentsArray, {
+ article_title: title,
+ user_id: current_user.id,
+ language: selectedWiki.language,
+ project: selectedWiki.project,
+ });
}
+ }
- return (
-
- );
- });
- list = (
- dispatch(addAssignment)}
+ deleteAssignment={() => dispatch(deleteAssignment)}
+ current_user={current_user}
/>
);
- }
-
- let loader;
- if (this.state.isSubmitted && this.props.loading) {
- loader = ;
- }
-
- let fetchMoreButton;
- if (this.props.continue_results && this.state.isSubmitted) {
- fetchMoreButton = (
-
- );
- }
-
- let searchStats;
- if (!this.props.loading && this.state.isSubmitted) {
- const fetchedCount = Object.keys(this.props.unfilteredArticles).length;
- const filteredCount = Object.keys(this.props.articles).length;
- searchStats = (
-
-
-
-
{fetchedCount}
-
{I18n.t(`article_finder.${ArticleUtils.projectSuffix(this.props.selectedWiki.project, 'fetched_articles')}`)}
-
-
-
{filteredCount}
-
{I18n.t(`article_finder.${ArticleUtils.projectSuffix(this.props.selectedWiki.project, 'filtered_articles')}`)}
-
-
-
- );
- }
-
- const loaderMessage = {
- ARTICLES_LOADING: I18n.t(`article_finder.${ArticleUtils.projectSuffix(this.props.selectedWiki.project, 'searching_articles')}`),
- TITLE_RECEIVED: I18n.t('article_finder.fetching_assessments'),
- PAGEASSESSMENT_RECEIVED: I18n.t('article_finder.fetching_revisions'),
- REVISION_RECEIVED: I18n.t('article_finder.fetching_scores'),
- REVISIONSCORE_RECEIVED: I18n.t('article_finder.fetching_pageviews'),
- };
-
- let fetchingLoader;
- if (this.props.fetchState !== 'PAGEVIEWS_RECEIVED' && !this.props.loading) {
- fetchingLoader = (
-
-
- {loaderMessage[this.props.fetchState]}
-
- );
- }
-
- const trackedWikis = trackedWikisMaker(this.props.course);
+ });
- const options = (
-
);
+ };
+ const fetchMoreButton = () => {
return (
-
-
-
- {options}
- {filterBlock}
-
- {searchStats}
-
- {fetchingLoader}
+
+ );
+ };
+
+ const getSearchStats = () => {
+ const fetchedCount = Object.keys(unfilteredArticles).length;
+ const filteredCount = Object.keys(articles).length;
+ return (
+
+
+
+
{fetchedCount}
+
+ {I18n.t(
+ `article_finder.${ArticleUtils.projectSuffix(
+ selectedWiki.project,
+ 'fetched_articles'
+ )}`
+ )}
+
-
- {fetchMoreButton}
+
+
{filteredCount}
+
+ {I18n.t(
+ `article_finder.${ArticleUtils.projectSuffix(
+ selectedWiki.project,
+ 'filtered_articles'
+ )}`
+ )}
+
- {loader}
- {list}
-
- {fetchMoreButton}
-
);
- }
-});
-
-const mapStateToProps = state => ({
- articles: getFilteredArticleFinder(state),
- unfilteredArticles: state.articleFinder.articles,
- wikidataLabels: state.wikidataLabels.labels,
- loading: state.articleFinder.loading,
- search_term: state.articleFinder.search_term,
- min_views: state.articleFinder.min_views,
- article_quality: state.articleFinder.article_quality,
- depth: state.articleFinder.depth,
- search_type: state.articleFinder.search_type,
- continue_results: state.articleFinder.continue_results,
- offset: state.articleFinder.offset,
- cmcontinue: state.articleFinder.cmcontinue,
- assignments: state.assignments.assignments,
- loadingAssignments: state.assignments.loading,
- fetchState: state.articleFinder.fetchState,
- sort: state.articleFinder.sort,
- home_wiki: state.articleFinder.home_wiki,
- selectedWiki: state.articleFinder.wiki || state.articleFinder.home_wiki
-});
-
-const mapDispatchToProps = {
- fetchCategoryResults: fetchCategoryResults,
- updateFields: updateFields,
- addAssignment: addAssignment,
- fetchAssignments: fetchAssignments,
- sortArticleFinder: sortArticleFinder,
- fetchKeywordResults: fetchKeywordResults,
- deleteAssignment: deleteAssignment,
- resetArticleFinder: resetArticleFinder,
- clearResults: clearResults,
+ };
+
+ const loaderMessage = {
+ ARTICLES_LOADING: I18n.t(
+ `article_finder.${ArticleUtils.projectSuffix(
+ selectedWiki.project,
+ 'searching_articles'
+ )}`
+ ),
+ TITLE_RECEIVED: I18n.t('article_finder.fetching_assessments'),
+ PAGEASSESSMENT_RECEIVED: I18n.t('article_finder.fetching_revisions'),
+ REVISION_RECEIVED: I18n.t('article_finder.fetching_scores'),
+ REVISIONSCORE_RECEIVED: I18n.t('article_finder.fetching_pageviews'),
+ };
+
+ const displayLoaderMessage = () => {
+ return (
+
+
+ {loaderMessage[fetchState]}
+
+ );
+ };
+
+ const options = (
+
+ );
+
+ const listStatus = isSubmitted && !loading;
+ const isLoadingStatus = isSubmitted && loading;
+ const isSubmittedButNotLoadingStatus = !loading && isSubmitted;
+ const pageViewsRecievedStatus = fetchState !== 'PAGEVIEWS_RECEIVED' && !loading;
+ const fetchMoreResultsStatus = continue_results && isSubmitted;
+
+ const trackedWikis = trackedWikisMaker(course);
+
+ return (
+
+
+
+ {options}
+ {filterBlock}
+
+ {isSubmittedButNotLoadingStatus ? getSearchStats() : null}
+
{pageViewsRecievedStatus ? displayLoaderMessage() : null}
+
{fetchMoreResultsStatus ? fetchMoreButton() : null}
+
+ {isLoadingStatus ?
: null}
+ {listStatus ? renderList() : null}
+
+ {fetchMoreResultsStatus ? fetchMoreButton() : null}
+
+
+ );
};
-export default compose(
- withRouter,
- connect(mapStateToProps, mapDispatchToProps)
-)(ArticleFinder);
+export default ArticleFinder;
diff --git a/app/assets/javascripts/components/article_finder/constants.js b/app/assets/javascripts/components/article_finder/constants.js
new file mode 100644
index 0000000000..5da5d22879
--- /dev/null
+++ b/app/assets/javascripts/components/article_finder/constants.js
@@ -0,0 +1,30 @@
+export const table_keys = {
+ relevanceIndex: {
+ label: I18n.t('article_finder.relevanceIndex'),
+ desktop_only: false,
+ },
+ title: {
+ label: I18n.t('articles.title'),
+ desktop_only: false,
+ },
+ grade: {
+ label: I18n.t('article_finder.page_assessment_class'),
+ desktop_only: false,
+ sortable: true,
+ },
+ revScore: {
+ label: I18n.t('article_finder.completeness_estimate'),
+ desktop_only: false,
+ sortable: true,
+ },
+ pageviews: {
+ label: I18n.t('article_finder.average_views'),
+ desktop_only: false,
+ sortable: true,
+ },
+ tools: {
+ label: I18n.t('article_finder.tools'),
+ desktop_only: false,
+ sortable: false,
+ },
+};
diff --git a/app/assets/javascripts/components/article_finder/hooks/useInitialiseArticleFinder.jsx b/app/assets/javascripts/components/article_finder/hooks/useInitialiseArticleFinder.jsx
new file mode 100644
index 0000000000..bdc569f0ac
--- /dev/null
+++ b/app/assets/javascripts/components/article_finder/hooks/useInitialiseArticleFinder.jsx
@@ -0,0 +1,78 @@
+import { getFilteredArticleFinder } from '../../../selectors';
+import { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+
+const useArticlefinderData = () => {
+ const articles = useSelector(state => getFilteredArticleFinder(state));
+ const unfilteredArticles = useSelector(
+ state => state.articleFinder.articles
+ );
+ const wikidataLabels = useSelector(state => state.wikidataLabels.labels);
+ const loading = useSelector(state => state.articleFinder.loading);
+ const search_term = useSelector(state => state.articleFinder.search_term);
+ const min_views = useSelector(state => state.articleFinder.min_views);
+ const article_quality = useSelector(
+ state => state.articleFinder.article_quality
+ );
+ const search_type = useSelector(state => state.articleFinder.search_type);
+ const continue_results = useSelector(
+ state => state.articleFinder.continue_results
+ );
+ const offset = useSelector(state => state.articleFinder.offset);
+ const cmcontinue = useSelector(state => state.articleFinder.cmcontinue);
+ const assignments = useSelector(state => state.assignments.assignments);
+ const loadingAssignments = useSelector(state => state.assignments.loading);
+ const fetchState = useSelector(state => state.articleFinder.fetchState);
+ const sort = useSelector(state => state.articleFinder.sort);
+ const home_wiki = useSelector(state => state.articleFinder.home_wiki);
+ const selectedWiki = useSelector(
+ state => state.articleFinder.wiki || state.articleFinder.home_wiki
+ );
+
+ useEffect(() => {
+ // build url if any of the depencency change
+ buildURL();
+ }, [
+ search_term,
+ search_type,
+ article_quality,
+ min_views,
+ selectedWiki,
+ home_wiki,
+ ]);
+
+ const buildURL = (search_value = '') => {
+ const itemToSearch = search_value || search_term;
+ let queryStringUrl = window.location.href.split('?')[0];
+ const params_array = { search_type, article_quality, min_views };
+ queryStringUrl += `?search_term=${itemToSearch}`;
+ Object.entries(params_array).forEach((param) => {
+ const [key, value] = param;
+ return (queryStringUrl += `&${key}=${value}`);
+ });
+ history.replaceState(window.location.href, 'query_string', queryStringUrl);
+ };
+
+ return {
+ articles,
+ unfilteredArticles,
+ wikidataLabels,
+ loading,
+ search_term,
+ min_views,
+ article_quality,
+ search_type,
+ continue_results,
+ offset,
+ cmcontinue,
+ assignments,
+ loadingAssignments,
+ fetchState,
+ sort,
+ home_wiki,
+ selectedWiki,
+ buildURL,
+ };
+};
+
+export default useArticlefinderData;