Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend changes for showing tabbed course overview stats #5064

Merged
merged 23 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
02da8da
Add namespace-id mapping and make required modifications.
vaidehi44 Jul 21, 2022
8c237a3
Add components to display namespace overview stats in tabs.
vaidehi44 Jul 21, 2022
a0bb519
Modify WikidataOverviewStats container class to adjust css.
vaidehi44 Jul 21, 2022
0c81559
Merge remote-tracking branch 'upstream/master' into namespace-stats-ui
vaidehi44 Jul 21, 2022
23821ba
Some eslint fixes.
vaidehi44 Jul 21, 2022
918faf0
Move stats title function to article utils.
vaidehi44 Jul 23, 2022
8f2d6ca
Refined some code.
vaidehi44 Jul 23, 2022
9cc4b44
Test for overview stats title util function.
vaidehi44 Jul 23, 2022
5670769
Minor changes
vaidehi44 Jul 24, 2022
7dd7f26
Lint fixes
vaidehi44 Jul 24, 2022
f0e4e0b
Fix ruby spec test.
vaidehi44 Jul 24, 2022
c0b47bc
New stats content component.
vaidehi44 Jul 26, 2022
ae7d15d
New UI for tabs.
vaidehi44 Jul 26, 2022
f019e01
Merge branch 'master' of https://github.com/WikiEducationFoundation/W…
vaidehi44 Jul 26, 2022
d8d7bf0
Add method to get namespace id from title.
vaidehi44 Jul 27, 2022
49b696b
Write ruby test for tabbed course stats
vaidehi44 Jul 28, 2022
f7c9833
Change tab id
vaidehi44 Jul 28, 2022
c170b1c
Update format method for all course stats
vaidehi44 Jul 28, 2022
82d042f
Merge remote-tracking branch 'upstream/master' into namespace-stats-ui
vaidehi44 Jul 28, 2022
8aea637
Update renderZero prop for all namespace attributes
vaidehi44 Jul 28, 2022
1639e9f
Update feature test
vaidehi44 Jul 29, 2022
34b8c98
Handle rendering of tabs if there's only one stats object
vaidehi44 Jul 30, 2022
6f58d37
Fix some Lint errors
vaidehi44 Jul 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import PropTypes from 'prop-types';
import OverviewStat from './overview_stat';

const NamespaceOverviewStats = ({ statistics }) => {
return (
<div className="stat-display">
<OverviewStat
id="articles-created"
className="stat-display__value"
stat={statistics.new_count}
statMsg={I18n.t('metrics.articles_created')}
renderZero={false}
/>
<OverviewStat
id="articles-edited"
className="stat-display__value"
stat = {statistics.edited_count}
statMsg={I18n.t('metrics.articles_edited')}
renderZero={false}
/>
<OverviewStat
id="total-edits"
className="stat-display__value"
stat = {statistics.revision_count}
statMsg={I18n.t('metrics.edit_count_description')}
renderZero={false}
/>
<OverviewStat
id="student-editors"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the user count needs to be in any namespace-specific stats.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't it give insight of how many students worked on particular tracked namespace?

className="stat-display__value"
stat={statistics.user_count}
statMsg={I18n.t('courses.student_editors')}
renderZero={false}
/>
<OverviewStat
id="word-count"
className="stat-display__value"
stat={statistics.word_count}
statMsg={I18n.t('metrics.word_count')}
renderZero={false}
/>
<OverviewStat
id="references-count"
className="stat-display__value"
stat={statistics.references_count}
statMsg={I18n.t('metrics.references_count')}
renderZero={false}
/>
<OverviewStat
id="view-count"
className="stat-display__value"
stat={statistics.views_count}
statMsg={I18n.t('metrics.view_count_description')}
renderZero={false}
/>
</div>
);
};

NamespaceOverviewStats.propTypes = {
statistics: PropTypes.object.isRequired
};

export default NamespaceOverviewStats;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';

import WikidataOverviewStats from '../wikidata_overview_stats';
import NamespaceOverviewStats from './namespace_overview_stats';

const OverviewStatsContent = ({ content }) => {
const title = content.statsTitle;
const data = content.statsData;
const statistics = (title === 'www.wikidata.org')
? <WikidataOverviewStats statistics={data} isCourseOverview={true}/>
: <NamespaceOverviewStats statistics={data} />;

return (
<div className="content-container">
<h2 className="title">{title}</h2>
<div className="stats-data">
{statistics}
</div>
</div>
);
};

OverviewStatsContent.propTypes = {
content: PropTypes.object.isRequired
};

export default OverviewStatsContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';

const OverviewStatsTab = ({ id, title, active, onClick }) => {
const isActive = (active) ? ' active' : '';
const tabClass = `tab${isActive}`;
const tabId = `tab-${id}`;
return (
<div className={tabClass} onClick={onClick} id={tabId}>
<p>{title}</p>
</div>
);
};

OverviewStatsTab.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
active: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired
};

export default OverviewStatsTab;
58 changes: 58 additions & 0 deletions app/assets/javascripts/components/common/overview_stats_tabs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import OverviewStatsTab from './OverviewStats/overview_stats_tab';
import OverviewStatsContent from './OverviewStats/overview_stats_content';

import { overviewStatsLabel } from '../../utils/wiki_utils';


const OverviewStatsTabs = ({ statistics }) => {
const [currentTabId, setCurrentTabId] = useState(0);

const onTabChange = (e) => {
const tabId = e.currentTarget.id;
const tabIdNumber = Number(tabId.split('-')[1]);
return setCurrentTabId(tabIdNumber);
};

const statsList = [];
const tabsList = [];

let index = 0;
Object.keys(statistics).forEach((wiki_ns_key) => {
const statsTitle = overviewStatsLabel(wiki_ns_key);
const statsData = statistics[wiki_ns_key];

statsList.push({ statsTitle, statsData });
tabsList.push(
<OverviewStatsTab
key={index}
id={index}
onClick={onTabChange}
title={statsTitle}
active={currentTabId === index}
/>
);
index += 1;
});

const content = <OverviewStatsContent content={statsList[currentTabId]} />;
// Hide tabs container if there is only one tab
const tabsClass = `tabs-container${(tabsList.length === 1) ? ' hide' : ''}`;

return (
<div className="overview-stats-tabs-container">
<div className={tabsClass}>
{tabsList}
</div>
{content}
</div>
);
};

OverviewStatsTabs.propTypes = {
statistics: PropTypes.object.isRequired
};

export default OverviewStatsTabs;
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import PropTypes from 'prop-types';
import OverviewStat from './OverviewStats/overview_stat';
import I18n from 'i18n-js';

const WikidataOverviewStats = ({ statistics }) => {
const WikidataOverviewStats = ({ statistics, isCourseOverview }) => {
let containerClass = 'wikidata-stats-container';
let title = 'Wikidata stats';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this title isn't used in the the latest screenshots you shared?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is used. In the last one. Here, the title isn't "Wikidata stats", but "www.wikidata.org". I thought that this would be in accordance with other namespace stats and their title. So, "Wikidata Stats" would be only shown for campaign overview stats. Is it okay?

if (isCourseOverview) {
containerClass = 'wikidata-stats-container course-overview';
title = null;
}
return (
<div className="wikidata-stats-container">
<h2 className="wikidata-stats-title">Wikidata stats</h2>
<div className={containerClass}>
<h2 className="wikidata-stats-title">{title}</h2>
<div className="wikidata-display">
<div className="stat-display__row">
<h5 className="stats-label">{I18n.t('metrics.general')}</h5>
Expand Down Expand Up @@ -216,7 +222,8 @@ const WikidataOverviewStats = ({ statistics }) => {
};

WikidataOverviewStats.propTypes = {
statistics: PropTypes.object
statistics: PropTypes.object.isRequired,
isCourseOverview: PropTypes.bool
};

export default WikidataOverviewStats;
12 changes: 8 additions & 4 deletions app/assets/javascripts/components/overview/overview_handler.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { fetchOnboardingAlert } from '../../actions/course_alert_actions';
import { fetchTags } from '../../actions/tag_actions';
import { addValidation, setValid, setInvalid, activateValidations } from '../../actions/validation_actions';
import { getStudentUsers, getWeeksArray, getAllWeeksArray, firstValidationErrorMessage, isValid } from '../../selectors';
import WikidataOverviewStats from '../common/wikidata_overview_stats';
import OverviewStatsTabs from '../common/overview_stats_tabs';

const Overview = createReactClass({
displayName: 'Overview',
Expand Down Expand Up @@ -164,13 +164,17 @@ const Overview = createReactClass({
) : (
<div className="sidebar" />
);

let overviewStatsTabs;
if (course.course_stats && course.course_stats.stats_hash) {
overviewStatsTabs = <OverviewStatsTabs statistics={course.course_stats.stats_hash}/>;
}

return (
<section className="overview container">
{ syllabusUpload }
<OverviewStats course={course} />
{course.course_stats && <WikidataOverviewStats
statistics={course.course_stats.stats_hash['www.wikidata.org']}
/>}
{overviewStatsTabs}
<StatisticsUpdateInfo course={course} />
{userArticles}
<div className="primary">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class StudentRevisionsList extends React.Component {
super(props);
this.state = {
isOpen: false,
namespace: 'all'
namespace: 'all' // show all namespace revisions by default
};

this.toggleDrawer = this.toggleDrawer.bind(this);
Expand All @@ -37,7 +37,8 @@ export class StudentRevisionsList extends React.Component {
revisions = (this.state.namespace === 'all')
? userRevisions[student.id]
: userRevisions[student.id].filter((rev) => {
return rev.article.namespace === ArticleUtils.namespaceToId(this.state.namespace);
const current_ns_id = ArticleUtils.getNamespaceId(this.state.namespace);
return rev.article.namespace === current_ns_id;
});
}
return revisions;
Expand Down Expand Up @@ -98,10 +99,10 @@ export class StudentRevisionsList extends React.Component {
value={this.state.namespace}
onChange={this.onNamespaceChange}
>
<option value="all">{I18n.t('namespace.filter.all_ns')}</option>
<option value="article">{I18n.t('namespace.filter.article_ns')}</option>
<option value="user">{I18n.t('namespace.filter.user_ns')}</option>
<option value="talk">{I18n.t('namespace.filter.talk_ns')}</option>
<option value={'all'}>{I18n.t('namespace.all')}</option>
<option value={'main'}>{I18n.t('namespace.main')}</option>
<option value={'user'}>{I18n.t('namespace.user')}</option>
<option value={'talk'}>{I18n.t('namespace.talk')}</option>
</select>
);
return (
Expand Down
3 changes: 2 additions & 1 deletion app/assets/javascripts/reducers/course.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ const initialState = {
loading: true,
updates: {},
wikis: [],
article_count: 0
article_count: 0,
course_stats: {}
};


Expand Down
51 changes: 43 additions & 8 deletions app/assets/javascripts/utils/article_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,48 @@ export default class ArticleUtils {
return project === 'wikidata' ? `${messageKey}_wikidata` : messageKey;
}

// Mapping of namespace to its id
static namespaceToId(namespace) {
const mapping = {
article: 0,
talk: 1,
user: 2
};
return mapping[namespace];
// The following mapping is also present in article.rb file and,
// wiki.rb has mapping of wikis to namespaces. Hence, any modifications
// in namespaces should also reflect in the corresponding files.
static NamespaceIdMapping = {
0: 'main',
1: 'talk',
2: 'user',
4: 'project',
6: 'file',
8: 'mediaWiki',
10: 'template',
12: 'help',
14: 'category',
104: 'page',
108: 'book',
110: 'wikijunior',
114: 'translation',
118: 'draft',
120: 'property',
122: 'query',
146: 'lexeme',
100: {
wiktionary: 'appendix',
wikisource: 'portal',
wikiversity: 'school'
},
102: {
wikisource: 'author',
wikibooks: 'cookbook',
wikiversity: 'portal'
},
106: {
wiktionary: 'rhymes',
wikisource: 'index',
wikiversity: 'collection'
}
};

// Get namespace id from its title
static getNamespaceId(namespace) {
const ns_id_mapping = this.NamespaceIdMapping;
const ns_id = Object.keys(ns_id_mapping).find(ns => ns_id_mapping[ns] === namespace);
return Number(ns_id);
}
}
19 changes: 18 additions & 1 deletion app/assets/javascripts/utils/wiki_utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import ArticleUtils from './article_utils';

const toWikiDomain = (wiki) => {
const subdomain = wiki.language || 'www';
return `${subdomain}.${wiki.project}.org`;
Expand All @@ -20,4 +22,19 @@ const trackedWikisMaker = (course) => {
}
return trackedWikis;
};
export { trackedWikisMaker, formatOption, toWikiDomain };

// Get label for tabs and stats data of tabbed course overview stats.
// Here, wiki_ns_key is key of a course_stats object, which
// identifies wiki or wiki-namespace of stats,
// eg.: 'en.wikibooks.org-namespace-102', 'www.wikidata.org'
const overviewStatsLabel = (wiki_ns_key) => {
// If stats are for wikidata overview, directly return the wiki domain
if (!wiki_ns_key.includes('namespace')) return wiki_ns_key;
const project = wiki_ns_key.split('.')[1];
const wiki_domain = wiki_ns_key.split('-')[0];
const ns_id = wiki_ns_key.split('-')[2];
let ns_title = ArticleUtils.NamespaceIdMapping[ns_id];
if (typeof (ns_title) !== 'string') ns_title = ns_title[project];
return `${wiki_domain} - ${I18n.t(`namespace.${ns_title}`)}`;
};
export { trackedWikisMaker, formatOption, toWikiDomain, overviewStatsLabel };
Loading