Skip to content

Commit

Permalink
Merge branch 'master' into fix-markdown-alignment
Browse files Browse the repository at this point in the history
  • Loading branch information
aoelen authored Apr 17, 2020
2 parents d052708 + 2a5260e commit 6c6c926
Show file tree
Hide file tree
Showing 18 changed files with 890 additions and 24 deletions.
24 changes: 24 additions & 0 deletions actions/deckQuality/loadDeckQuality.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const log = require('../log/clog');
const maxQuestions = 100; // number of questions per page
const pageNum = 1; // pagination

export default function loadDeckQuality(context, payload, done) {
log.info(context);
context.dispatch('UPDATE_MODULE_TYPE_SUCCESS', {moduleType: 'quality'});

// questions are also evaluated in the quality check, so let's fetch them
payload.params.maxQ = payload.params.maxQ === undefined || parseInt(payload.params.maxQ) === 'NaN' ? maxQuestions : payload.params.maxQ;
payload.params.pageNum = payload.params.pageNum === undefined || parseInt(payload.params.pageNum) === 'NaN' ? pageNum : payload.params.pageNum;

context.service.read('questions.list', payload, {timeout: 20 * 1000}, (err, res) => {
if (err) {
log.error(context, {filepath: __filename});
context.executeAction(serviceUnavailable, payload, done);
} else {
context.dispatch('LOAD_CONTENT_QUESTIONS_SUCCESS', res);
}

done();
});

}
16 changes: 14 additions & 2 deletions components/Deck/ContentModulesPanel/ContentModulesPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import loadContentUsage from '../../../actions/loadContentUsage';
import loadContentQuestions from '../../../actions/loadContentQuestions';
import loadDataSources from '../../../actions/datasource/loadDataSources';
import loadTags from '../../../actions/tags/loadTags';
import loadDeckQuality from '../../../actions/deckQuality/loadDeckQuality';
//import loadContributors from '../../../actions/loadContributors';
import ContentHistoryPanel from './ContentHistoryPanel/ContentHistoryPanel';
import ContentUsagePanel from './ContentUsagePanel/ContentUsagePanel';
import ContentDiscussionPanel from './ContentDiscussionPanel/ContentDiscussionPanel';
import ContentQuestionsPanel from './ContentQuestionsPanel/ContentQuestionsPanel';
import DataSourcePanel from './DataSourcePanel/DataSourcePanel';
import TagsPanel from './TagsPanel/TagsPanel';
import QualityPanel from './QualityPanel/QualityPanel';
//import ContributorsPanel from './ContributorsPanel/ContributorsPanel';
import ContentModulesStore from '../../../stores/ContentModulesStore';
import PermissionsStore from '../../../stores/PermissionsStore';
Expand Down Expand Up @@ -103,6 +105,9 @@ class ContentModulesPanel extends React.Component {
case 'playlists':
this.context.executeAction(loadCollectionsTab, {params: this.props.ContentModulesStore.selector});
break;
case 'quality':
this.context.executeAction(loadDeckQuality, {params: this.props.ContentModulesStore.selector});
break;
//case 'contributors':
// this.context.executeAction(loadContributors, {params: this.props.ContentModulesStore.selector});
// break;
Expand Down Expand Up @@ -203,7 +208,11 @@ class ContentModulesPanel extends React.Component {
<span> ({this.props.ContentModulesStore.moduleCount.playlists})</span>}
</span>,
value: 'playlists'
}
},
{
text: 'Quality',
value: 'quality'
},
];
}

Expand Down Expand Up @@ -247,6 +256,9 @@ class ContentModulesPanel extends React.Component {
case 'playlists':
activityDIV = <CollectionsPanel selector={this.props.ContentModulesStore.selector} id="playlist_panel" aria-labelledby="playlist_label"/>;
break;
case 'quality':
activityDIV = <QualityPanel selector={this.props.ContentModulesStore.selector} id="quality_panel" aria-labelledby="quality_label"/>;
break;
default:
activityDIV = <ContentDiscussionPanel selector={this.props.ContentModulesStore.selector} id="comments_panel" aria-labelledby="comments_label"/>;
}
Expand All @@ -267,7 +279,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' || item.value === 'questions')) {
if (this.props.ContentModulesStore.selector.stype !== 'deck' && (item.value === 'tags' || item.value === 'playlists' || item.value === 'questions' || item.value === 'quality')) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ class ContentQuestionEdit extends React.Component {
}
});
swal({
title: context.intl.formatMessage(swal_messages.text),
title: this.context.intl.formatMessage(swal_messages.text),
type: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: context.intl.formatMessage(swal_messages.confirmButtonText),
confirmButtonText: this.context.intl.formatMessage(swal_messages.confirmButtonText),
}).then((accepted) => {
this.context.executeAction(deleteQuestion, {questionId: this.state.qid});
}, (reason) => {/*do nothing*/}).catch(swal.noop);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import PropTypes from 'prop-types';
import React from 'react';
import { connectToStores } from 'fluxible-addons-react';
import DeckViewStore from '../../../../stores/DeckViewStore';
import { FormattedMessage, defineMessages } from 'react-intl';
import { Accordion, Icon, Label, Message, List } from 'semantic-ui-react';

class CheckDescriptiveNames extends React.Component {
constructor() {
super();

this.state = {
errors: [],
};

this.maxWords = 10;
this.minWords = 3;
this.maxLength = 50;
}

componentDidMount() {
this.runValidation();
}

// rerun the validation when the decks are updated
componentDidUpdate(prevProps) {
if (this.props.DeckViewStore !== prevProps.DeckViewStore) {
this.runValidation();
}
}

validateTitle = (title) => {
if (!title) {
return false;
}
const wordsAmount = title.split(' ').length;

// check the amount of words in the title
if (wordsAmount < this.minWords || wordsAmount > this.maxWords) {
return false;
}

// check the amount of characters
if (wordsAmount.length > this.maxLength) {
return false;
}

return true;
};

runValidation = () => {
const deckTitle = this.props.DeckViewStore.deckData.title;
let errors = [];

if (!this.validateTitle(deckTitle)) {
errors.push({
title: 'Deck title: ' + deckTitle,
description: 'Deck title is not descriptive, change the name',
});
}

const slides = this.props.DeckViewStore.slidesData.children;

if (slides && slides.length > 0) {
for (const slide of slides) {
if (!this.validateTitle(slide.title)) {
errors.push({
title: 'Slide title: ' + slide.title,
description: 'Slide name is not descriptive, change the name',
});
}
}
}


this.setState({
errors,
});
};

render() {
return (
<>
<Accordion.Title active={this.props.activeIndex === this.props.index} index={this.props.index} onClick={this.props.handleClick}>
<Icon name='dropdown' />
Short and descriptive names{' '}
<Label color={this.state.errors.length > 0 ? 'red' : 'green'} horizontal>
{this.state.errors.length > 0 ? this.state.errors.length + ' issues' : 'All good'}
</Label>
</Accordion.Title>
<Accordion.Content active={this.props.activeIndex === this.props.index}>
<Message color='blue'>Both the deck name and slide names should be short and descriptive</Message>
<List divided relaxed>
{this.state.errors.map((error, index) => (
<List.Item key={index}>
<List.Icon name='file' size='large' verticalAlign='middle' />
<List.Content>
<List.Header>{error.title}</List.Header>
<List.Description>{error.description}</List.Description>
</List.Content>
</List.Item>
))}
</List>
</Accordion.Content>
</>
);
}
}

CheckDescriptiveNames.contextTypes = {
intl: PropTypes.object.isRequired,
};

CheckDescriptiveNames = connectToStores(CheckDescriptiveNames, [DeckViewStore], (context, props) => {
return {
DeckViewStore: context.getStore(DeckViewStore).getState(),
};
});

export default CheckDescriptiveNames;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import PropTypes from 'prop-types';
import React from 'react';
import { connectToStores } from 'fluxible-addons-react';
import DeckViewStore from '../../../../stores/DeckViewStore';
import { FormattedMessage, defineMessages } from 'react-intl';
import { Accordion, Icon, Label, Message, List } from 'semantic-ui-react';

class CheckHierarchicalDesign extends React.Component {
constructor() {
super();

this.state = {
error: false,
};
}

componentDidMount() {
this.runValidation();
}

componentDidUpdate(prevProps) {
if (this.props.DeckViewStore !== prevProps.DeckViewStore) {
this.runValidation();
}
}

runValidation = () => {
const contentItems = this.props.DeckViewStore.deckData.contentItems;
let foundDeck = false;

if (contentItems && contentItems.length > 0) {
for (const item of contentItems) {
if (item.kind === 'deck') {
foundDeck = true;
break;
}
}
}

this.setState({
error: !foundDeck,
});
};

render() {
return (
<>
<Accordion.Title active={this.props.activeIndex === this.props.index} index={this.props.index} onClick={this.props.handleClick}>
<Icon name='dropdown' />
Hierarchical design{' '}
<Label color={this.state.error ? 'red' : 'green'} horizontal>
{this.state.error ? '1 issue' : 'All good'}
</Label>
</Accordion.Title>
<Accordion.Content active={this.props.activeIndex === this.props.index}>
<Message color='blue'>It is good practice to use subdecks to create a hierarchy in the slide structure</Message>
<List divided relaxed>
{this.state.error && <Message color='red'>No subdecks found, add a subdeck to improve the slide structure</Message>}
</List>
</Accordion.Content>
</>
);
}
}

CheckHierarchicalDesign.contextTypes = {
intl: PropTypes.object.isRequired,
};

CheckHierarchicalDesign = connectToStores(CheckHierarchicalDesign, [DeckViewStore], (context, props) => {
return {
DeckViewStore: context.getStore(DeckViewStore).getState(),
};
});

export default CheckHierarchicalDesign;
Loading

0 comments on commit 6c6c926

Please sign in to comment.