Skip to content
Snippets Groups Projects
Commit 1411f349 authored by Remco Tukker's avatar Remco Tukker
Browse files

continued work on the admin surveys page

parent e0027976
No related branches found
No related tags found
1 merge request!52Feature/admin workflow surveys page
......@@ -28,16 +28,6 @@ class VerificationStatus(str, Enum):
Edited = "edited" # a question for which last years answer was edited
@routes.route('/nrens', methods=['GET'])
@common.require_accepts_json
def get_nrens() -> Any:
entries = [
{"id": entry.id, "name": entry.name}
for entry in db.session.scalars(select(NREN).order_by(NREN.name))
]
return jsonify(entries)
# TODO admin only
@routes.route('/new', methods=['POST'])
@common.require_accepts_json
......@@ -50,6 +40,9 @@ def start_new_survey() -> Any:
select(Survey).order_by(Survey.year.desc()).limit(1)
)
if not last_survey:
return "No survey found", 404
new_year = last_survey.year + 1
new_survey = last_survey.survey
new_survey = Survey(year=new_year, survey=new_survey, status=SurveyStatus.closed)
......@@ -70,7 +63,9 @@ def open_survey(year) -> Any:
if survey.status != SurveyStatus.closed:
return "Survey is not closed and can therefore not be opened", 400
# TODO check if there are no other open surveys
all_surveys = db.session.scalars(select(Survey))
if any([s.status == SurveyStatus.open for s in all_surveys]):
return "There already is an open survey", 400
survey.status = SurveyStatus.open
db.session.commit()
......@@ -94,7 +89,27 @@ def close_survey(year) -> Any:
return {'success': True}
# TODO publish
# TODO admin only
@routes.route('/publish/<int:year>', methods=['POST'])
@common.require_accepts_json
def publish_survey(year) -> Any:
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return "Survey not found", 404
if survey.status not in [SurveyStatus.closed, SurveyStatus.published]:
return "Survey is not closed or published and can therefore not be published", 400
if any([response.status != ResponseStatus.checked for response in survey.responses]):
return "There are responses that arent checked yet", 400
# TODO call new survey_publisher with all responses and the year
survey.status = SurveyStatus.published
db.session.commit()
return {'success': True}
# TODO admin only
......@@ -119,24 +134,45 @@ def list_surveys() -> Any:
for entry in surveys
]
# TODO i suppose we should also add response entries for the nrens that didnt start the survey, with a not started status? (only when open?)
# TODO i suppose we should also add response entries for the nrens that didnt start the survey, with
# a not started status? (only when open?)
# TODO fix the ordering of the responses by nren name, i couldnt convince SA yet..
return jsonify(entries)
@routes.route('/respond/<string:nren_name>', methods=['GET'])
# TODO admin only
@routes.route('/try/<int:year>', methods=['GET'])
@common.require_accepts_json
def respond_to_survey(nren_name) -> Any:
def try_survey(year) -> Any:
return get_survey(year, all_visible=False)
# just a hardcoded year for development for now
nren = db.session.execute(select(NREN).filter(NREN.name == nren_name)).scalar_one()
year = 1989
last_year = 2022
# TODO admin only
@routes.route('/inspect/<int:year>', methods=['GET'])
@common.require_accepts_json
def inspect_survey(year) -> Any:
return get_survey(year, all_visible=True)
def get_survey(year, all_visible) -> Any:
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if survey is None or survey.survey == {}:
if not survey:
return "Survey not found", 404
survey_json = prepare_survey_model(survey.survey, year, all_visible)
return jsonify({
"model": survey_json,
"data": None,
"page": 0,
"verification_status": {}
})
def prepare_survey_model(survey, year, all_visible=False):
if survey is None or survey == {}:
# TODO remove this at some point, its just convenient for now while we are changing the survey model a lot
# or is it really?? we can also keep the surveys on disk, which makes it really easy to diff, etc
p = Path(__file__).with_name('survey_model.json')
......@@ -144,8 +180,9 @@ def respond_to_survey(nren_name) -> Any:
survey = json.load(f)
replacements = {
"[%websiteurlregex%]": r"^(https?:\/\/)?([\da-zA-Z\.-]+\.[a-zA-Z\.]{2,6}|[\d\.]+)([\/:?=&#%]{1}[\d_a-zA-Z\.-]+)*[\/\?]?$",
"[%lastyear%]": str(last_year),
"[%websiteurlregex%]":
r"^(https?:\/\/)?([\da-zA-Z\.-]+\.[a-zA-Z\.]{2,6}|[\d\.]+)([\/:?=&#%]{1}[\d_a-zA-Z\.-]+)*[\/\?]?$",
"[%lastyear%]": str(year - 1),
"[%surveyyear%]": str(year)
}
......@@ -160,15 +197,40 @@ def respond_to_survey(nren_name) -> Any:
visitor(value, value.items())
elif type(value) == list:
visitor(value, enumerate(value))
elif all_visible and key == 'visibleIf':
object['title'] = object['title'] + ' (visibleif: [' + value.replace('{', '#').replace('}', '#') + '])'
object[key] = 'true'
elif type(value) == str:
object[key] = replacer(value)
visitor(survey, survey.items())
return survey
@routes.route('/load/<int:year>/<string:nren_name>', methods=['GET'])
@common.require_accepts_json
def load_survey(year, nren_name) -> Any:
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if not nren:
return "NREN not found", 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return "Survey not found", 404
# TODO validation (if not admin) on year (is survey open?) and nren (logged in user is part of nren?)
survey_json = prepare_survey_model(survey.survey, year)
data: Optional[dict] = None
page = 0
verification_status = {}
# just a hardcoded year for development for now TODO should be removed
year = 1989
last_year = 2022
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year).where(SurveyResponse.nren_id == nren.id)
)
......@@ -186,20 +248,20 @@ def respond_to_survey(nren_name) -> Any:
data = previous_response_data
verification_status = {question_name: VerificationStatus.Unverified for question_name in data.keys()}
open_survey: dict = {
"model": survey,
return jsonify({
"model": survey_json,
"data": data,
"page": page,
"verification_status": verification_status
}
return jsonify(open_survey)
})
@routes.route('/save/<string:nren_name>', methods=['POST'])
@routes.route('/save/<int:year>/<string:nren_name>', methods=['POST'])
@common.require_accepts_json
def save_survey(nren_name) -> Any:
# TODO validation (if not admin) on year (is survey open?) and nren (logged in user is part of nren?)
# just a hardcoded year for development for now
nren = db.session.execute(select(NREN).filter(NREN.name == nren_name)).scalar_one()
year = 1989
......@@ -226,6 +288,8 @@ def save_survey(nren_name) -> Any:
}
# TODO set status on complete or checked when necessary
# if admin: completed: set to checked / not completed: leave as is (started if new)
# if nren: completed: set to completed / not completed: leave as is (started if new)
db.session.commit()
......
......@@ -2,9 +2,9 @@ import React, { ReactElement } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ShowUser from './ShowUser';
import SurveyManagementComponent from './SurveyManagementComponent'
import SurveySelectionComponent from './SurveySelectionComponent';
import SurveyManagementComponent from './SurveyManagementComponent';
import UserManagementComponent from './UserManagementComponent';
import SurveyComponent from "./SurveyComponent";
function App(): ReactElement {
......@@ -12,9 +12,11 @@ function App(): ReactElement {
<div className="app">
<Router>
<Routes>
<Route path="survey/admin/surveysdata" element={<SurveySelectionComponent />} />
<Route path="survey/admin/surveys" element={<SurveyManagementComponent />} />
<Route path="survey/admin/users" element={<UserManagementComponent />} />
<Route path="survey/admin/inspect/:year" element={<SurveyComponent loadFrom={'/api/survey/inspect/'} />}/>
<Route path="survey/admin/try/:year" element={<SurveyComponent loadFrom={'/api/survey/try/'} />}/>
<Route path="survey/respond/:year/:nren" element={<SurveyComponent loadFrom={'/api/survey/load/'} saveTo={'/api/survey/save/'} />}/>
<Route path="*" element={<ShowUser />} />
</Routes>
</Router>
......
......@@ -4,6 +4,7 @@ import { Survey } from "survey-react-ui";
import "survey-core/modern.min.css";
import './survey.scss';
import ProgressBar from "./ProgressBar";
import { useParams } from "react-router-dom";
Serializer.addProperty("itemvalue", "customDescription:text");
Serializer.addProperty("question", "hideCheckboxLabels:boolean");
......@@ -23,10 +24,11 @@ enum VerificationStatus {
}
function SurveyComponent({ nrenName }) {
function SurveyComponent({ loadFrom, saveTo = ''}) {
const [surveyModel, setSurveyModel] = useState<Model>();
const [progress, setProgress] = useState<Progress[]>([]);
const verificationStatus = useRef<Map<string, VerificationStatus>>(new Map());
const { year, nren } = useParams();
function setVerifyButton(question: Question, state: VerificationStatus) {
......@@ -63,8 +65,11 @@ function SurveyComponent({ nrenName }) {
// }, []);
function saveSurveyData (survey, success?, failure?) {
if (saveTo == '') {
return;
}
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/survey/save/" + nrenName);
xhr.open("POST", saveTo + nren);
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.onload = xhr.onerror = () => {
if (xhr.status == 200 && success) {
......@@ -82,7 +87,7 @@ function SurveyComponent({ nrenName }) {
}
async function getModel() {
const response = await fetch('/api/survey/respond/' + nrenName);
const response = await fetch(loadFrom + (year ?? '') + (nren ?? '')) // one of year and nren is set
const json = await response.json();
for (const questionName in json["verification_status"]) {
......
......@@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react";
import Accordion from 'react-bootstrap/Accordion';
import Button from 'react-bootstrap/Button';
import Table from 'react-bootstrap/Table';
import { useNavigate } from 'react-router-dom';
async function fetchSurveys(): Promise<Survey[]> {
......@@ -54,6 +55,8 @@ function SurveyManagementComponent() {
const newSurveyAllowed = surveys.every(s => s.status == 'published');
const openSurveys = surveys.some(s => s.status == 'open');
const navigate = useNavigate();
return (
<div>
<Button onClick={newSurvey} disabled={!newSurveyAllowed}>start new survey</Button>
......@@ -61,10 +64,11 @@ function SurveyManagementComponent() {
{surveys.map(survey => (
<Accordion.Item eventKey={survey.year.toString()} key={survey.year}>
<Accordion.Header>{survey.year} - {survey.status} -
<Button>inspect - show whole survey with all questions visible</Button>
<Button onClick={() => navigate(`/survey/admin/inspect/${survey.year}`)}>inspect</Button>
<Button onClick={() => navigate(`/survey/admin/try/${survey.year}`)}>try</Button>
<Button onClick={() => postSurveyStatus(survey.year, 'open')} disabled={openSurveys || survey.status!='closed'}>open</Button>
<Button onClick={() => postSurveyStatus(survey.year, 'close')} disabled={survey.status!='open'}>close</Button>
<Button onClick={() => postSurveyStatus(survey.year, 'publish')} disabled={survey.status!='closed' || !survey.responses.every(r => r.status == 'checked')}>publish</Button>
<Button onClick={() => postSurveyStatus(survey.year, 'publish')} disabled={(survey.status!='closed' && survey.status!='published') || !survey.responses.every(r => r.status == 'checked')}>publish</Button>
</Accordion.Header>
<Accordion.Body>
<Table>
......
import React, { useState, useEffect } from "react";
import SurveyComponent from "./SurveyComponent";
interface Nren {
id: number
name: string
}
function SurveySelectionComponent() {
const [nrens, setNrens] = useState<Nren[]>([]);
const [selectedNren, setSelectedNren] = useState<Nren | null>(null);
useEffect(() => {
// Fetch organizations from the API
fetchNrens();
}, []);
const fetchNrens = async () => {
try {
const response = await fetch('/api/survey/nrens');
const data = await response.json();
setNrens(data);
} catch (error) {
console.error('Error fetching organizations:', error);
}
};
const handleNrenSelect = (nren) => {
setSelectedNren(nren);
};
if (!selectedNren) {
return (
<div>
<h2>Select an organization:</h2>
<ul>
{nrens.map((nren) => (
<li key={nren.id} onClick={() => handleNrenSelect(nren)}>
{nren.name}
</li>
))}
</ul>
</div>
);
}
return <SurveyComponent nrenName={selectedNren.name} />;
}
export default SurveySelectionComponent;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment