Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
 into SWIK-2459-Add-deck-statistics-dashboard
  • Loading branch information
stavmars committed Nov 1, 2018
2 parents 21becc7 + 983a31b commit b233185
Show file tree
Hide file tree
Showing 20 changed files with 574 additions and 125 deletions.
2 changes: 1 addition & 1 deletion actions/history/revertRevision.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function revertRevision(context, payload, done) {
} else {
//if reverting root deck
if (payload.selector.id === payload.selector.sid) {
newURL = '/deck/' + newSid;
newURL = `/deck/${newSid}/_/deck/${newSid}`;
}
//update the URL, pass runFetchTree parameter to force deck tree fetching when reverting a subdeck
context.executeAction(navigateAction, {
Expand Down
4 changes: 4 additions & 0 deletions actions/search/loadSearchResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export default function loadSearchResults(context, payload, done) {
payload.query.tag = [payload.query.tag];
}

if (payload.query.educationLevel && !isArray(payload.query.educationLevel)) {
payload.query.educationLevel = [payload.query.educationLevel];
}

if (payload.query.facet_exclude && !isArray(payload.query.facet_exclude)) {
payload.query.facet_exclude = [payload.query.facet_exclude];
}
Expand Down
21 changes: 21 additions & 0 deletions actions/stats/loadUserEngagement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import UserProfileStore from '../../stores/UserProfileStore';
const log = require('../log/clog');
import serviceUnavailable from '../error/serviceUnavailable';


export default function loadUserStatsByTag(context, payload, done) {
log.info(context);
let username = context.getStore(UserProfileStore).username;

context.dispatch('SET_USER_ENGAGEMENT_LOADING');

context.service.read('stats.userEngagement', {username}, {timeout: 20 * 1000}, (err, res) => {
if (err) {
log.error(context, {filepath: __filename});
context.executeAction(serviceUnavailable, payload, done);
} else {
context.dispatch('LOAD_USER_ENGAGEMENT', {userEngagement: res});
}
done();
});
}
5 changes: 5 additions & 0 deletions actions/stats/loadUserStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const log = require('../log/clog');
import serviceUnavailable from '../error/serviceUnavailable';
import loadUserStatsByTime from './loadUserStatsByTime';
import loadUserStatsByTag from './loadUserStatsByTag';
import loadUserEngagement from './loadUserEngagement';



export default function loadUserStats(context, payload, done) {
Expand All @@ -14,6 +16,9 @@ export default function loadUserStats(context, payload, done) {
(callback) => {
context.executeAction(loadUserStatsByTag, payload, callback);
},
(callback) => {
context.executeAction(loadUserEngagement, payload, callback);
},
], (err, results) => {
if (err) {
log.error(context, {filepath: __filename});
Expand Down
2 changes: 1 addition & 1 deletion components/AddDeck/AddDeck.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class AddDeck extends React.Component {
//console.log('AddDeck: handleRedirect()');
this.context.executeAction(importFinished, {}); // destroy import components state
this.context.executeAction(navigateAction, {
url: '/deck/' + this.props.AddDeckStore.redirectID
url: '/deck/' + this.props.AddDeckStore.redirectID + '/_/deck/' + this.props.AddDeckStore.redirectID
});
}
handleImportRedirect() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class DeckRevision extends React.Component {

handleViewRevisionClick() {
//open the deck revision in a new tab
window.open('/deck/' + this.props.selector.sid.split('-')[0] + '-' + this.props.revision.id, '_blank');
const redirectId = this.props.selector.sid.split('-')[0] + '-' + this.props.revision.id;
window.open(`/deck/${redirectId}/_/deck/${redirectId}`, '_blank');
}


Expand Down
2 changes: 1 addition & 1 deletion components/Deck/ContentModulesPanel/ContentModulesPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class ContentModulesPanel extends React.Component {
});

//hide tags and playlists for slide view
if (this.props.ContentModulesStore.selector.stype !== 'deck' && (item.value === 'tags' || item.value === 'playlists')) {
if (this.props.ContentModulesStore.selector.stype !== 'deck' && (item.value === 'tags' || item.value === 'playlists' || item.value === 'questions')) {
return;
}

Expand Down
30 changes: 16 additions & 14 deletions components/Deck/TreePanel/TreePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ForkModal from './ForkModal';
import NavigationPanel from './../NavigationPanel/NavigationPanel';
import TranslationStore from '../../../stores/TranslationStore';
import updateTrap from '../../../actions/loginModal/updateTrap';
import { makeNodeURL } from '../../common/Util';

class TreePanel extends React.Component {

Expand Down Expand Up @@ -103,24 +104,25 @@ class TreePanel extends React.Component {
}

getPresentationHref(){
let presLocation = ['/presentation', this.props.DeckTreeStore.selector.toJS().id, this.props.deckSlug || '_'].join('/') + '/';
let selector = this.props.DeckTreeStore.selector.toJS();
selector.subdeck = this.getCurrentSubdeck(selector); //subdeck is not part of the selector from DeckTreeStore, so manually add it

return makeNodeURL(selector, 'presentation', undefined, this.props.deckSlug, this.props.TranslationStore.currentLang);
}

getCurrentSubdeck(selector){
let currentSubDeck;
let splitSpath = selector.spath.split(';');

if (this.props.DeckTreeStore.selector.toJS().spath.search(';') !== -1)
{
//if a subdeck is selected - use its selector
presLocation += this.props.DeckTreeStore.selector.toJS().spath.substring(0, this.props.DeckTreeStore.selector.toJS().spath.search(';')) + '/';
} else {
//if it is the main/root deck - use that id
presLocation += this.props.DeckTreeStore.selector.toJS().id + '/';
if(!selector.spath || (splitSpath.length === 1 && selector.stype === 'slide')){
return null;
}
if(this.props.DeckTreeStore.selector.toJS().stype === 'slide'){
//if it is a slide, also add ID of slide
presLocation += this.props.DeckTreeStore.selector.toJS().sid;// + '/';
else if(selector.stype === 'deck' && selector.sid){
return selector.sid;
}
if (this.props.TranslationStore.currentLang) {
presLocation += '?language=' + (this.props.TranslationStore.currentLang);
else{
return splitSpath[0].split(':')[0];
}
return presLocation;
}

handleTheme() {
Expand Down
49 changes: 47 additions & 2 deletions components/Search/SearchPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import querystring from 'querystring';
import KeywordsInputWithFilter from './AutocompleteComponents/KeywordsInputWithFilter';
import SpellcheckPanel from './SearchResultsPanel/SpellcheckPanel';
import { Divider } from 'semantic-ui-react';
import { educationLevels } from '../../lib/isced';
import { Dropdown } from 'semantic-ui-react';

class SearchPanel extends React.Component {
constructor(props){
Expand Down Expand Up @@ -196,6 +198,7 @@ class SearchPanel extends React.Component {
language: null,
user: null,
tag: null,
educationLevel: null,
facet_exclude: [],
};

Expand All @@ -221,6 +224,13 @@ class SearchPanel extends React.Component {
this.tagDropdown.clear();
}

let educationLevel = this.educationLevelsDropdown.state.value;
if (educationLevel) {
advancedFilters.educationLevel = educationLevel;
advancedFilters.facet_exclude.push('educationLevel');
this.educationLevelsDropdown.setValue(null);
}

return advancedFilters;
}
applyAdvancedFilters(filters) {
Expand Down Expand Up @@ -253,6 +263,15 @@ class SearchPanel extends React.Component {
}
}

if (filters.educationLevel) {
if (newState.educationLevel) {
newState.educationLevel = newState.educationLevel.concat(filters.educationLevel);
newState.educationLevel = uniq(newState.educationLevel);
} else {
newState.educationLevel = filters.educationLevel;
}
}

if (filters.facet_exclude) {
newState.facet_exclude = filters.facet_exclude;
}
Expand Down Expand Up @@ -283,6 +302,7 @@ class SearchPanel extends React.Component {
language: filters.language || [],
tag: filters.tag || [],
user: filters.user || [],
educationLevel: filters.educationLevel || [],
facet_exclude: filters.facet_exclude || [],
}, () => {
this.handleSearch(params);
Expand Down Expand Up @@ -389,14 +409,21 @@ class SearchPanel extends React.Component {
newState.user = [];
}

if (fieldName === 'educationLevel' || fieldName === 'all') {
newState.educationLevel = [];
}

newState.facet_exclude = this.getSelectedFacetFields();

this.setState(newState, () => {
this.handleRedirect(null, 'facets');
});
}
render() {
let advanced_options = <div className="three fields">
let firstRowOptions = <div className="three fields">
<div className="sr-only" id="describe_level">Select education level of deck content</div>
<div className="sr-only" id="describe_topic">Select subject of deck content from autocomplete</div>

<div className="field">
<label htmlFor="language"><FormattedMessage {...this.messages.languageFilterTitle} /></label>
<select id='languageDropdown' name='language' multiple='' className='ui fluid search dropdown' ref='language'>
Expand All @@ -407,6 +434,22 @@ class SearchPanel extends React.Component {
}, [])}
</select>
</div>
<div className="field">
<label htmlFor="level_input" id="level_label"><FormattedMessage id="DeckProperty.Education" defaultMessage="Education Level" /></label>
<Dropdown id="level_input" ref={ (e) => { this.educationLevelsDropdown = e; }} fluid selection aria-labelledby="level_label" aria-describedby="describe_level"
options={ [{ value: null, text: '' }, ...Object.entries(educationLevels).map(([value, text]) => ({value, text}) )] }
placeholder="Select Education Level" />
</div>
<div className="field">
{
// <label htmlFor="topics_input_field" id="topics_label"><FormattedMessage id="DeckProperty.Tag.Topic" defaultMessage="Subject" /></label>
// <TagInput id="topics_input_field" aria-labelledby="topics_label" aria-describedby="describe_topic"
// ref={(i) => (this.topicInput = i)} tagFilter={{ tagType: 'topic' }} initialTags={this.state.topics} />
}
</div>
</div>;

let secondRowOptions = <div className="two fields">
<div className="field">
<label htmlFor="users_input_field"><FormattedMessage {...this.messages.usersFilterTitle} /></label>
<UsersInput ref={ (e) => { this.userDropdown = e; }} placeholder={this.context.intl.formatMessage(this.messages.usersFilterPlaceholder)} />
Expand All @@ -430,7 +473,8 @@ class SearchPanel extends React.Component {
Advanced Options
</div>
<div className="content field">
{ advanced_options }
{ firstRowOptions }
{ secondRowOptions }
</div>
</div>
</div>
Expand Down Expand Up @@ -458,6 +502,7 @@ class SearchPanel extends React.Component {
languages: this.state.language || [],
tags: this.state.tag || [],
users: this.state.user || [],
educationLevel: this.state.educationLevel || [],
}}
loading={this.props.SearchResultsStore.loading}
error={this.props.SearchResultsStore.error}
Expand Down
32 changes: 31 additions & 1 deletion components/Search/SearchResultsPanel/Facets.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class Facets extends React.Component {
id: 'Facets.tagsFacet',
defaultMessage: 'Tags'
},
educationLevelFacet: {
id: 'Facets.educationLevelFacet',
defaultMessage: 'Education Level'
},
showMore: {
id: 'Facets.showMore',
defaultMessage: 'show more'
Expand Down Expand Up @@ -214,6 +218,7 @@ class Facets extends React.Component {
const languageItems = this.getFacetItems(data.language, 'language', selected.languages);
const userItems = this.getFacetItems(data.creator, 'user', selected.users);
const tagItems = this.getFacetItems(data.tags, 'tag', selected.tags);
const educationLevelItems = this.getFacetItems(data.educationLevel, 'educationLevel', selected.educationLevel);

return (
<div id="facets">
Expand Down Expand Up @@ -273,6 +278,30 @@ class Facets extends React.Component {
</Menu>
}

{
<Menu fluid vertical>
<Menu.Item key="educationLevelHeader" header>
{this.context.intl.formatMessage(this.messages.educationLevelFacet)}
{
// <NavLink href="#" onClick={this.handleShowFacetSearch.bind(this, 'tags')}>
// <Icon name="search" />
// </NavLink>
}
{
(!isEmpty(selected.educationLevel) && !this.props.loading)
? <span style={{float: 'right'}}>
<NavLink href="#" onClick={this.clearFacets.bind(this, 'educationLevel')}>
<Icon name="cancel" aria-label="clear education levels"/>
</NavLink>
</span>
: ''
}
</Menu.Item>

{ educationLevelItems }
</Menu>
}

{
<Menu fluid vertical>
<Menu.Item key="tagFacetHeader" header>
Expand All @@ -295,11 +324,12 @@ class Facets extends React.Component {
{ (this.state.showSearch.includes('tags')) &&
<Menu.Item>
<Input id="tags_input" name="tag" placeholder='Search for tags' onChange={this.handleFacetsFilter.bind(this, 'tags')} onKeyDown={this.handleFacetsFilterKeyPress.bind(this, 'tags')}/>
</Menu.Item>
</Menu.Item>
}
{ tagItems }
</Menu>
}

</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion components/Stats/ActivityTimeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ActivityTimeline extends React.Component {
return (
<div>
<Message attached>
<h3>{this.context.intl.formatMessage(this.messages.activityTimelineTitle)}</h3>
<h2>{this.context.intl.formatMessage(this.messages.activityTimelineTitle)}</h2>
</Message>
<Segment attached padded loading={this.props.loading}>
<span>
Expand Down
2 changes: 1 addition & 1 deletion components/Stats/UserBarChart.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class UserBarChart extends React.Component {
return (
<div>
<Message attached>
<h3>{this.props.title}</h3>
<h2>{this.props.title}</h2>
</Message>
<Segment attached padded loading={this.props.loading}>
<span>
Expand Down
Loading

0 comments on commit b233185

Please sign in to comment.