-
Notifications
You must be signed in to change notification settings - Fork 631
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
Changes from 5 commits
02da8da
8c237a3
a0bb519
0c81559
23821ba
918faf0
8f2d6ca
9cc4b44
5670769
7dd7f26
f0e4e0b
c0b47bc
ae7d15d
f019e01
d8d7bf0
49b696b
f7c9833
c170b1c
82d042f
8aea637
1639e9f
34b8c98
6f58d37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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={true} | ||
/> | ||
<OverviewStat | ||
id="total-edits" | ||
className="stat-display__value" | ||
stat = {statistics.revision_count} | ||
statMsg={I18n.t('metrics.edit_count_description')} | ||
renderZero={true} | ||
/> | ||
<OverviewStat | ||
id="student-editors" | ||
className="stat-display__value" | ||
stat={statistics.user_count} | ||
statMsg={I18n.t('courses.student_editors')} | ||
renderZero={true} | ||
/> | ||
<OverviewStat | ||
id="word-count" | ||
className="stat-display__value" | ||
stat={statistics.word_count} | ||
statMsg={I18n.t('metrics.word_count')} | ||
renderZero={true} | ||
/> | ||
<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={true} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
NamespaceOverviewStats.propTypes = { | ||
statistics: PropTypes.object.isRequired | ||
}; | ||
|
||
export default NamespaceOverviewStats; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
const OverviewStatsTab = ({ id, title, active, onClick }) => { | ||
const isActive = (active) ? ' active' : ''; | ||
const tabClass = `tab${isActive}`; | ||
return ( | ||
<div className={tabClass} onClick={onClick} id={id}> | ||
<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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React, { useState } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import OverviewStatsTab from './OverviewStats/overview_stats_tab'; | ||
import NamespaceOverviewStats from './OverviewStats/namespace_overview_stats'; | ||
import WikidataOverviewStats from './wikidata_overview_stats'; | ||
|
||
import ArticleUtils from '../../utils/article_utils'; | ||
|
||
|
||
const OverviewStatsTabs = ({ statistics }) => { | ||
const [currentTabId, setCurrentTabId] = useState(0); | ||
|
||
const getTabTitle = (ns_id, wiki) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two functions should probably be in a utils file rather than in this component. Tests for them would also be good. |
||
const project = wiki.split('.')[1]; | ||
let ns_title = ArticleUtils.NamespaceIdMapping[ns_id]; | ||
if (typeof (ns_title) !== 'string') ns_title = ns_title[project]; | ||
return `${I18n.t(`namespace.${ns_title}`)} (${wiki})`; | ||
}; | ||
|
||
const getContentTitle = (ns_id, wiki) => { | ||
const project = wiki.split('.')[1]; | ||
let ns_title = ArticleUtils.NamespaceIdMapping[ns_id]; | ||
if (typeof (ns_title) !== 'string') ns_title = ns_title[project]; | ||
return `Stats for ${I18n.t(`namespace.${ns_title}`)} (${wiki})`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This message shouldn't be hard-coded in English. Per the design image I shared, we might not need a message like this anyway; the label for each tab could just be the domain plus the namespace name (when there is one). |
||
}; | ||
|
||
const onTabChange = (e) => { | ||
return setCurrentTabId(Number(e.currentTarget.id)); | ||
}; | ||
|
||
const hasWikidataStats = () => { | ||
if (statistics['www.wikidata.org']) return true; | ||
return false; | ||
}; | ||
|
||
const statsDataList = []; | ||
// if there are wikidata overview stats, they should be displayed on first tab | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and the forEach loop are more data manipulation than I'd like to see. If possible, it would be best to do this in a way that doesn't special-case Wikidata. (I don't think it necessarily needs to be first.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I got this conceptual question. For each tab we have a different content rendered by different component. So, is it better to create this components, store and use them (like I am doing now), or is it better to initialize and render them instantaneously when a tab is selected? I think in our case, the compoents are not that heavy and so they can probably be stored before hand. |
||
if (hasWikidataStats) { | ||
statsDataList.push({ | ||
id: 0, | ||
tabTitle: 'Wikidata', | ||
contentTitle: null, // title for wikidata stats already exists in WikidataOverviewStats component | ||
data: statistics['www.wikidata.org'] | ||
}); | ||
} | ||
|
||
let index = hasWikidataStats ? 1 : 0; | ||
Object.keys(statistics).forEach((wiki_ns) => { | ||
if (!wiki_ns.includes('namespace')) return; // return if it doesn't contain namespace stats | ||
|
||
const ns_id = Number(wiki_ns.split('-')[2]); // namespace id | ||
const wiki = wiki_ns.split('-')[0]; | ||
const id = index; | ||
const tabTitle = getTabTitle(ns_id, wiki); | ||
const contentTitle = getContentTitle(ns_id, wiki); | ||
const data = statistics[wiki_ns]; | ||
statsDataList.push({ id, tabTitle, contentTitle, data }); | ||
index += 1; | ||
}); | ||
|
||
const tabsList = statsDataList.map((obj) => { | ||
return ( | ||
<OverviewStatsTab | ||
key={obj.id} | ||
id={obj.id} | ||
onClick={onTabChange} | ||
title={obj.tabTitle} | ||
active={currentTabId === obj.id} | ||
/> | ||
); | ||
}); | ||
|
||
const contentTitle = statsDataList[currentTabId].contentTitle; | ||
const data = statsDataList[currentTabId].data; | ||
const content = (hasWikidataStats && currentTabId === 0) | ||
? <WikidataOverviewStats statistics={data} isCourseOverview={true}/> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might make sense here to implement another simple component (eg, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I have already created a component named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, And yes, I think having this component identify wikidata vs namespace stats should be done in that new component. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ragesoss, I have created the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, it looks good to me. |
||
: <NamespaceOverviewStats statistics={data} />; | ||
|
||
return ( | ||
<div className="overview-stats-tabs-container"> | ||
<div className="tabs-container"> | ||
{tabsList} | ||
</div> | ||
<div className="content-container"> | ||
<h2 className="title">{contentTitle}</h2> | ||
<div className="stats-data"> | ||
{content} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
OverviewStatsTabs.propTypes = { | ||
statistics: PropTypes.object.isRequired | ||
}; | ||
|
||
export default OverviewStatsTabs; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,15 +8,14 @@ import StudentRevisionRow from './StudentRevisionRow'; | |
|
||
// Libraries | ||
import CourseUtils from '~/app/assets/javascripts/utils/course_utils.js'; | ||
import ArticleUtils from '~/app/assets/javascripts/utils/article_utils.js'; | ||
import studentListKeys from '@components/students/shared/StudentList/student_list_keys.js'; | ||
|
||
export class StudentRevisionsList extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
isOpen: false, | ||
namespace: 'all' | ||
namespace: -1 // show all namespace revisions by default | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For a code comprehensibility perspective, I don't think replacing strings with numbers here is helpful. Do the changes here have any effect on behavior? If they are unrelated to displaying stats on the Overview tab, they probably shouldn't be included in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These changes are related to the namespace filter of revisions table. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is okay, except... is there any reason to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I don't know what number to use for 'all', because for other options it would be their namespace id. May be, we can still use the strings - 'all', 'article', 'talk', etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a mix of numbers (for actual namespaces) and a string for |
||
}; | ||
|
||
this.toggleDrawer = this.toggleDrawer.bind(this); | ||
|
@@ -25,7 +24,7 @@ export class StudentRevisionsList extends React.Component { | |
onNamespaceChange = (e) => { | ||
// Open the drawer when filter is used | ||
if (!this.state.isOpen) this.setState({ isOpen: true }); | ||
return this.setState({ namespace: e.target.value }); | ||
return this.setState({ namespace: Number(e.target.value) }); | ||
}; | ||
|
||
// filter the revisions according to namespace | ||
|
@@ -34,10 +33,10 @@ export class StudentRevisionsList extends React.Component { | |
|
||
let revisions = []; | ||
if (userRevisions[student.id] !== undefined && userRevisions[student.id] !== null) { | ||
revisions = (this.state.namespace === 'all') | ||
revisions = (this.state.namespace === -1) | ||
? userRevisions[student.id] | ||
: userRevisions[student.id].filter((rev) => { | ||
return rev.article.namespace === ArticleUtils.namespaceToId(this.state.namespace); | ||
return rev.article.namespace === this.state.namespace; | ||
}); | ||
} | ||
return revisions; | ||
|
@@ -98,10 +97,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={-1}>{I18n.t('namespace.all')}</option> | ||
<option value={0}>{I18n.t('namespace.article')}</option> | ||
<option value={2}>{I18n.t('namespace.user')}</option> | ||
<option value={1}>{I18n.t('namespace.talk')}</option> | ||
</select> | ||
); | ||
return ( | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?