diff --git a/package.json b/package.json index 9d6a044dff..0ac6ceecb9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "cypress:open": "CYPRESS_baseUrl=http://localhost:8081 cypress open", "cypress:run": "CYPRESS_baseUrl=http://localhost:8081 cypress run --config video=false -P ./", - "cypress:record": "cypress run --config videoUploadOnPasses=false --record -P ./", + "cypress:record": "cypress run --config videoUploadOnPasses=false -P ./", "concurrently": "concurrently", "test": "./run_all_tests.sh", "test_services": "./run_service_tests.sh" diff --git a/services/backend/oodikone2-backend/src/database/connection.js b/services/backend/oodikone2-backend/src/database/connection.js index 55128ac956..4ab25bb761 100644 --- a/services/backend/oodikone2-backend/src/database/connection.js +++ b/services/backend/oodikone2-backend/src/database/connection.js @@ -5,21 +5,16 @@ const conf = require('../conf-backend') const sequelize = new Sequelize(conf.DB_URL, { schema: conf.DB_SCHEMA, logging: false, - operatorsAliases: false, - dialectOptions: { - prependSearchPath: true - } + operatorsAliases: false }) +sequelize.query(`SET SESSION search_path to ${conf.DB_SCHEMA}`) const sequelizeKone = new Sequelize(conf.DB_URL, { schema: conf.DB_SCHEMA_KONE, logging: false, - operatorsAliases: false, - searchPath: conf.DB_SCHEMA_KONE, - dialectOptions: { - prependSearchPath: true - } + operatorsAliases: false }) +sequelizeKone.query(`SET SESSION search_path to ${conf.DB_SCHEMA_KONE}`) // See https://github.com/sequelize/sequelize/issues/10875 const runMigrations = async () => { try { diff --git a/services/oodikone2-frontend/src/components/CourseStatistics/SearchForm/index.jsx b/services/oodikone2-frontend/src/components/CourseStatistics/SearchForm/index.jsx index 0077c0befc..80bd969eaa 100644 --- a/services/oodikone2-frontend/src/components/CourseStatistics/SearchForm/index.jsx +++ b/services/oodikone2-frontend/src/components/CourseStatistics/SearchForm/index.jsx @@ -1,10 +1,12 @@ import React, { Component } from 'react' import { Segment, Header, Form } from 'semantic-ui-react' import { connect } from 'react-redux' +import { withRouter } from 'react-router' +import qs from 'query-string' import { func, arrayOf, shape, bool } from 'prop-types' import { getSemesters } from '../../../redux/semesters' import { clearCourses, findCoursesV2 } from '../../../redux/coursesearch' -import { getCourseStats } from '../../../redux/coursestats' +import { getCourseStats, clearCourseStats } from '../../../redux/coursestats' import AutoSubmitSearchInput from '../../AutoSubmitSearchInput' import CourseTable from '../CourseTable' import { getCourseSearchResults } from '../../../selectors/courses' @@ -30,8 +32,22 @@ class SearchForm extends Component { } componentDidMount() { - this.props.getSemesters() - this.props.clearCourses() + const { location } = this.props + if (location.search) { + console.log(location.search) + this.fetchStatisticsFromUrlParams() + } else { + this.props.getSemesters() + this.props.clearCourses() + } + } + + componentDidUpdate(prevProps) { + const { location } = this.props + const queryParamsChanged = prevProps.location.search !== this.props.location.search + if (location.search && queryParamsChanged) { + this.fetchStatisticsFromUrlParams() + } } onSelectCourse = (course) => { @@ -69,7 +85,36 @@ class SearchForm extends Component { separate } - await this.props.getCourseStats(params) + // await this.props.getCourseStats(params) + this.pushQueryToUrl(params) + } + + fetchStatisticsFromUrlParams() { + const query = this.parseQueryFromUrl() + this.setState({ ...query, selectedcourses: query.courseCodes }) + this.props.getCourseStats(query) + } + + pushQueryToUrl = (query) => { + const { history } = this.props + const { courseCodes, ...rest } = query + const queryObject = { ...rest, courseCodes: JSON.stringify(courseCodes) } + const searchString = qs.stringify(queryObject) + history.push({ search: searchString }) + } + + parseQueryFromUrl = () => { + const { location } = this.props + const { courseCodes, fromYear, toYear, separate, ...rest } = qs.parse(location.search) + const query = { + ...this.state.INITIAL, + ...rest, + courseCodes: JSON.parse(courseCodes), + fromYear: JSON.parse(fromYear), + toYear: JSON.parse(toYear), + separate: JSON.parse(separate) + } + return query } fetchCourses = () => { @@ -103,7 +148,6 @@ class SearchForm extends Component { coursename, coursecode } = this.state - const courses = matchingCourses.filter(c => !selectedcourses[c.code]) const disabled = (!fromYear || Object.keys(selectedcourses).length === 0) || isLoading @@ -187,7 +231,9 @@ SearchForm.propTypes = { matchingCourses: arrayOf(shape({})).isRequired, years: arrayOf(shape({})).isRequired, isLoading: bool.isRequired, - coursesLoading: bool.isRequired + coursesLoading: bool.isRequired, + history: shape({}).isRequired, + location: shape({}).isRequired } const mapStateToProps = (state) => { @@ -205,9 +251,10 @@ const mapStateToProps = (state) => { } } -export default connect(mapStateToProps, { +export default withRouter(connect(mapStateToProps, { getSemesters, getCourseStats, clearCourses, - findCoursesV2 -})(SearchForm) + findCoursesV2, + clearCourseStats +})(SearchForm)) diff --git a/services/oodikone2-frontend/src/components/PopulationStudents/index.jsx b/services/oodikone2-frontend/src/components/PopulationStudents/index.jsx index 791c75e39f..360946b4bd 100644 --- a/services/oodikone2-frontend/src/components/PopulationStudents/index.jsx +++ b/services/oodikone2-frontend/src/components/PopulationStudents/index.jsx @@ -93,12 +93,12 @@ class PopulationStudents extends Component { const priorityText = (studyRights) => { const codes = this.studyrightCodes(studyRights, 'prioritycode') - return codes.map(code => PRIORITYCODE_TEXTS[code] ? PRIORITYCODE_TEXTS[code] : code).join(', ') // eslint-disable-line + return codes.map(code => (PRIORITYCODE_TEXTS[code] ? PRIORITYCODE_TEXTS[code] : code)).join(', ') } const extentCodes = (studyRights) => { const codes = this.studyrightCodes(studyRights, 'extentcode') - return codes.join(', ') // eslint-disable-line + return codes.join(', ') } const studytrack = studyrights => ( @@ -290,22 +290,29 @@ class PopulationStudents extends Component { ['asc', 'asc'] ) + const labelColumns = [] + if (mandatoryCourseLabels.reduce((acc, e) => acc + e.length, 0) > 0) { + labelColumns.push( + { + key: 'general', + title: Labels:, + parent: true, + headerProps: { colSpan: nameColumns.length, style: { textAlign: 'right' } } + }, + ...mandatoryCourseLabels.map(e => ({ + key: e, + title: ( +
{e}
+ ), + parent: true, + headerProps: { colSpan: labelToMandatoryCourses[e].length, width: labelToMandatoryCourses[e].length, title: e } + })) + ) + } + const mandatoryCourseColumns = [ ...nameColumns, - ...(mandatoryCourseLabels.reduce((acc, e) => acc + e.length, 0) > 0) ? [{ - key: 'general', - title: Labels:, - parent: true, - headerProps: { colSpan: nameColumns.length, style: { textAlign: 'right' } } - }] : [], - ...mandatoryCourseLabels.map(e => ({ - key: e, - title: ( -
{e}
- ), - parent: true, - headerProps: { colSpan: labelToMandatoryCourses[e].length, width: labelToMandatoryCourses[e].length, title: e } - })), + ...labelColumns, ..._.flatten(mandatoryCourseLabels.map(e => _.sortBy( labelToMandatoryCourses[e], [(m) => { diff --git a/services/oodikone2-frontend/src/components/StudentDetails/index.jsx b/services/oodikone2-frontend/src/components/StudentDetails/index.jsx index c96eff79c0..46ba8fb506 100644 --- a/services/oodikone2-frontend/src/components/StudentDetails/index.jsx +++ b/services/oodikone2-frontend/src/components/StudentDetails/index.jsx @@ -3,12 +3,16 @@ import { func, shape, string, boolean, arrayOf, integer } from 'prop-types' import { connect } from 'react-redux' import { Segment, Table, Icon } from 'semantic-ui-react' import { isEmpty, sortBy } from 'lodash' +import moment from 'moment' +import qs from 'query-string' import { withRouter } from 'react-router-dom' import { getStudent, removeStudentSelection, resetStudent } from '../../redux/students' import StudentInfoCard from '../StudentInfoCard' import CreditAccumulationGraph from '../CreditAccumulationGraph' import SearchResultTable from '../SearchResultTable' import { byDateDesc, reformatDate, getTextIn } from '../../common' +import { clearCourseStats } from '../../redux/coursestats' + class StudentDetails extends Component { componentDidMount() { @@ -26,6 +30,16 @@ class StudentDetails extends Component { } } + pushQueryToUrl = (query) => { + const { history } = this.props + const { courseCodes, ...rest } = query + const queryObject = { ...rest, courseCodes: JSON.stringify(courseCodes) } + const searchString = qs.stringify(queryObject) + this.props.clearCourseStats() + history.push('/coursestatistics/') + history.push({ search: searchString }) + } + renderCreditsGraph = () => { const { translate, student } = this.props return ( @@ -60,11 +74,13 @@ class StudentDetails extends Component { } else { icon = } + const year = -(moment(new Date('1.1.1950')).diff(new Date('2004'), 'years') - 1) // :D return [ reformatDate(date, 'DD.MM.YYYY'), `${isStudyModuleCredit ? `${getTextIn(course.name, language)} [Study Module]` : getTextIn(course.name, language)} (${course.code})`,
{icon}{grade}
, - credits + credits, + this.pushQueryToUrl({ courseCodes: [course.code], separate: false, fromYear: year, toYear: year })} /> ] }) return ( @@ -204,6 +220,7 @@ StudentDetails.propTypes = { removeStudentSelection: func.isRequired, studentNumber: string, translate: func.isRequired, + clearCourseStats: func.isRequired, student: shape({ courses: arrayOf(shape({ course: shape({ @@ -235,6 +252,7 @@ const mapStateToProps = ({ students, settings }) => ({ }) const mapDispatchToProps = dispatch => ({ removeStudentSelection: () => dispatch(removeStudentSelection()), + clearCourseStats: () => dispatch(clearCourseStats()), resetStudent: () => dispatch(resetStudent()), getStudent: studentNumber => dispatch(getStudent(studentNumber)) diff --git a/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeSelector/index.jsx b/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeSelector/index.jsx index a7e4aeed2b..8fcbb94784 100644 --- a/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeSelector/index.jsx +++ b/services/oodikone2-frontend/src/components/StudyProgramme/StudyProgrammeSelector/index.jsx @@ -51,7 +51,6 @@ class StudyProgrammeSelector extends Component { getRowKey={programme => programme.code} getRowProps={programme => ({ onClick: () => this.props.handleSelect(programme.code), style: { cursor: 'pointer' } })} data={studyprogrammes} - defaultdescending /> ) }