Commit 828cb5b0 authored by Christopher Mark Fullarton's avatar Christopher Mark Fullarton

Merge remote-tracking branch 'origin/master'

parents 341379c1 ab3eb3df
{"export":{"page_header":"&LQuiz-Statistik und Ranglisten generiert in einer Live-Session mit arsnova.click&R__createdAt__","page_footer":"&L&A&Carsnova.click ist Open Source Software, siehe arsnova.io&R &P/&N","summary":"Zusammenfassung","exported_at_date":"Exportiert am","session_content":"Archivierte Quizfragen (Um das Quiz zu importieren, kopiere den vollständigen Zellinhalt in eine leere .json-Datei. Importiere dann die Datei mit dem entsprechenden Icon in der Fußzeile der App.)","cas_account_id":"CAS Account-ID","cas_account_email":"CAS E-Mail","quiz_name":"Quizname","question":"Quizfrage","question_type":"Typ der Quizfrage","number_of_answers":"Anzahl der Antworten","percent_correct":"Korrekte Antworten (in %)","answer":"Antwort","attendee":"Teilnehmer/in","attendee_complete_correct":"Teilnehmer/innen mit vollständig richtig beantworteten Fragen","attendee_complete_correct_none_available":"Keine Einträge vorhanden","correct_questions_none_available":"Keine","attendee_all_entries":"Alle Teilnehmer/innen","time":"Antwortzeit (in ms)","confidence_level":"Antwortsicherheit","correct_value":"Richtiger Wert","min_range":"Mindestwert","max_range":"Maximalwert","correct_questions":"Richtig beantwortete Fragen","overall_response_time":"Kumulierte Antwortzeit (in ms)","average_response_time":"Durchschnittliche Antwortzeit (in ms)","number_attendees":"Anzahl der Teilnehmer/innen","average_number_attendees_participated":"Durchschnittliche Teilnahmen am Quiz (in %)","average_correct_answered_questions":"Durchschnittlich richtig beantwortete Fragen","average_confidence":"Durchschnittliche Antwortsicherheit (in %)","exported_at":"um","exported_at_time":"Uhr","type":{"SurveyQuestion":"Umfragen","TrueFalseSingleChoiceQuestion":"Wahr-Falsch Frage","YesNoSingleChoiceQuestion":"Ja-Nein Frage","ABCDSingleChoiceQuestion":"ABCD Single-Choice Frage","SingleChoiceQuestion":"Single-Choice Frage","RangedQuestion":"Schätzfrage","FreeTextQuestion":"Freitext Frage","MultipleChoiceQuestion":"Multiple-Choice Frage"}},"view":{"answeroptions":{"free_text_question":{"config_case_sensitive":"Groß- und Kleinschreibung beachten","config_trim_whitespaces":"Leerzeichen entfernen","config_use_keywords":"Reihenfolge der Wörter beachten","config_use_punctuation":"Interpunktion beachten"}}},"global":{"no":"Nein","yes":"Ja"}}
\ No newline at end of file
{
"export": {
"page_header": "&LQuiz-Statistik und Ranglisten generiert in einer Live-Session mit arsnova.click&R__createdAt__",
"page_footer": "&L&A&Carsnova.click ist Open Source Software, siehe arsnova.io&R &P/&N",
"summary": "Zusammenfassung",
"exported_at_date": "Exportiert am",
"session_content": "Archivierte Quizfragen (Um das Quiz zu importieren, kopiere den vollständigen Zellinhalt in eine leere .json-Datei. Importiere dann die Datei mit dem entsprechenden Icon in der Fußzeile der App.)",
"cas_account_id": "CAS Account-ID",
"cas_account_email": "CAS E-Mail",
"quiz_name": "Quizname",
"question": "Quizfrage",
"question_type": "Typ der Quizfrage",
"number_of_answers": "Anzahl der Antworten",
"percent_correct": "Korrekte Antworten (in %)",
"answer": "Antwort",
"attendee": "Teilnehmer/in",
"attendee_complete_correct": "Teilnehmer/innen mit vollständig richtig beantworteten Fragen",
"attendee_complete_correct_none_available": "Keine Einträge vorhanden",
"correct_questions_none_available": "Keine",
"attendee_all_entries": "Alle Teilnehmer/innen",
"time": "Antwortzeit (in ms)",
"confidence_level": "Antwortsicherheit",
"correct_value": "Richtiger Wert",
"min_range": "Mindestwert",
"max_range": "Maximalwert",
"correct_questions": "Richtig beantwortete Fragen",
"overall_response_time": "Kumulierte Antwortzeit (in ms)",
"average_response_time": "Durchschnittliche Antwortzeit (in ms)",
"number_attendees": "Anzahl der Teilnehmer/innen",
"average_number_attendees_participated": "Durchschnittliche Teilnahmen am Quiz (in %)",
"average_correct_answered_questions": "Durchschnittlich richtig beantwortete Fragen",
"average_confidence": "Durchschnittliche Antwortsicherheit (in %)",
"exported_at": "um",
"exported_at_time": "Uhr",
"type": {
"SurveyQuestion": "Umfrage",
"TrueFalseSingleChoiceQuestion": "Wahr-Falsch Frage",
"YesNoSingleChoiceQuestion": "Ja-Nein Frage",
"ABCDSingleChoiceQuestion": "ABCD Single-Choice Frage",
"SingleChoiceQuestion": "Single-Choice Frage",
"RangedQuestion": "Schätzfrage",
"FreeTextQuestion": "Freitext Frage",
"MultipleChoiceQuestion": "Multiple-Choice Frage"
}
},
"view": {
"answeroptions": {
"free_text_question": {
"config_case_sensitive": "Groß- und Kleinschreibung beachten",
"config_trim_whitespaces": "Leerzeichen entfernen",
"config_use_keywords": "Reihenfolge der Wörter beachten",
"config_use_punctuation": "Interpunktion beachten"
}
}
},
"global": {
"no": "Nein",
"yes": "Ja"
}
}
\ No newline at end of file
......@@ -40,7 +40,7 @@ class DumpCryptor {
let salt = null;
if (fs.existsSync(this.pathToSalt)) {
salt = JSON.parse(fs.readFileSync(this.pathToSalt));
salt = JSON.parse(fs.readFileSync(this.pathToSalt, {encoding: 'UTF-8'}));
} else {
const bytesInSalt = 128 / 8;
salt = CryptoJS.lib.WordArray.random(bytesInSalt);
......
......@@ -15,80 +15,73 @@
"dependency-check": "npx --ignore-existing madge --circular --extensions ts src"
},
"dependencies": {
"api-spec-converter": "^2.7.32",
"body-parser": "^1.18.3",
"bull": "^3.7.0",
"api-spec-converter": "^2.8.3",
"body-parser": "^1.19.0",
"bunyan": "^1.8.12",
"cas": "0.0.3",
"compression": "^1.7.4",
"connect-busboy": "0.0.2",
"cookie-parser": "^1.4.4",
"cors": "^2.8.5",
"crypto-js": "^3.1.9-1",
"cssstyle": "^1.2.2",
"excel4node": "^1.7.1",
"express": "^4.16.4",
"excel4node": "^1.7.2",
"express": "^4.17.1",
"express-ws": "^4.0.0",
"file-type": "^10.10.0",
"file-type": "^12.0.0",
"gitlab": "^4.5.1",
"i18n": "^0.8.3",
"jsonwebtoken": "^8.5.1",
"lowdb": "^1.0.0",
"mathjax-node": "^2.1.1",
"messageformat": "^2.1.0",
"mime": "^2.4.2",
"mime-types": "^2.1.22",
"messageformat": "^2.2.1",
"mime": "^2.4.4",
"mime-types": "^2.1.24",
"minimist": "^1.2.0",
"mongoose": "^5.4.22",
"morgan": "^1.9.1",
"multer": "^1.4.1",
"node-sass-middleware": "0.11.0",
"nodemailer": "^5.1.1",
"nodemailer": "^6.2.1",
"reflect-metadata": "^0.1.13",
"request": "^2.88.0",
"request-promise-native": "^1.0.7",
"routing-controllers": "^0.7.7",
"routing-controllers-openapi": "^1.6.0",
"source-map-support": "^0.5.11",
"swagger-ui-express": "^4.0.2",
"typegoose": "^5.6.0",
"url-join": "^4.0.0",
"ws": "^6.2.1",
"routing-controllers-openapi": "^1.7.0",
"source-map-support": "^0.5.12",
"swagger-ui-express": "^4.0.6",
"typegoose": "^5.7.2",
"ws": "^7.0.1",
"xml2js": "^0.4.19"
},
"devDependencies": {
"@types/body-parser": "1.17.0",
"@types/bull": "^3.5.11",
"@types/bull": "^3.5.14",
"@types/bunyan": "^1.8.6",
"@types/busboy": "^0.2.3",
"@types/chai": "^4.1.7",
"@types/chai-http": "3.0.5",
"@types/chai-http": "4.2.0",
"@types/compression": "^0.0.36",
"@types/cors": "^2.8.4",
"@types/cors": "^2.8.5",
"@types/crypto-js": "^3.1.43",
"@types/express": "^4.16.1",
"@types/express": "^4.17.0",
"@types/file-type": "^10.9.1",
"@types/i18n": "^0.8.5",
"@types/jsonwebtoken": "^8.3.2",
"@types/lowdb": "^1.0.7",
"@types/minimist": "^1.2.0",
"@types/mocha": "^5.2.6",
"@types/mocha": "^5.2.7",
"@types/mongoose": "^5.3.24",
"@types/morgan": "^1.7.35",
"@types/node": "^10.14.4",
"@types/node": "^12.0.10",
"@types/request": "^2.48.1",
"@types/request-promise-native": "^1.0.15",
"@types/request-promise-native": "^1.0.16",
"@types/websocket": "0.0.40",
"@types/ws": "^6.0.1",
"@types/xml2js": "^0.4.4",
"chai": "^4.2.0",
"chai-http": "^4.2.1",
"mocha": "^5.2.0",
"chai-http": "^4.3.0",
"mocha": "^6.1.4",
"mocha-typescript": "^1.1.17",
"nyc": "^13.3.0",
"ts-loader": "^5.3.3",
"ts-node": "^8.0.3",
"tslint": "^5.15.0",
"typescript": "^3.4.2"
"nyc": "^14.1.1",
"ts-loader": "^6.0.4",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"typescript": "^3.5.2"
}
}
......@@ -63,11 +63,9 @@ class MongoDbConnector {
this._mongoURL += `${mongoHost}:${mongoPort}/${mongoDatabase}`;
const mongoURLOptions = [];
if (process.env.MONGODB_REPLICA_NAME) {
mongoURLOptions.push(`replicaSet=${mongoReplSet}`);
}
mongoURLOptions.push(`replicaSet=${mongoReplSet}`);
if (process.env.MONGODB_AUTH_SOURCE) {
if (Boolean(process.env.MONGODB_AUTH_SOURCE)) {
if (process.env.MONGODB_AUTH_SOURCE === 'true') {
mongoURLOptions.push(`authSource=${process.env.MONGODB_AUTH_SOURCE}`);
}
} else {
......
......@@ -94,6 +94,14 @@ class UserDAO extends AbstractDAO<{ [key: string]: IUserEntity }> {
return this.storage[name];
}
public getUserByTokenHash(tokenHash: string): IUserEntity {
if (this.isEmptyVars(tokenHash)) {
return null;
}
return Object.values(this.storage).find(user => user.tokenHash === tokenHash);
}
public getUserById(id: ObjectId): IUserEntity {
return Object.values(this.storage).find(val => val.id.equals(id));
}
......
......@@ -6,6 +6,16 @@ import { AuthService } from '../services/AuthService';
import { AbstractEntity } from './AbstractEntity';
export class UserEntity extends AbstractEntity implements IUserEntity {
private _tokenHash: string;
get tokenHash(): string {
return this._tokenHash;
}
set tokenHash(value: string) {
this._tokenHash = value;
}
private _token: string;
get token(): string {
......@@ -73,6 +83,7 @@ export class UserEntity extends AbstractEntity implements IUserEntity {
this._name = data.name;
this._privateKey = data.privateKey;
this._passwordHash = data.passwordHash;
this._tokenHash = data.tokenHash;
this._gitlabToken = data.gitlabToken;
this._token = data.token;
this._userAuthorizations = data.userAuthorizations.map(val => UserRole[val]);
......@@ -94,6 +105,7 @@ export class UserEntity extends AbstractEntity implements IUserEntity {
token: this.token,
name: this.name,
passwordHash: this.passwordHash,
tokenHash: this.tokenHash,
privateKey: this.privateKey,
gitlabToken: this.gitlabToken,
userAuthorizations: this.userAuthorizations,
......
......@@ -183,7 +183,7 @@ export class MemberEntity extends AbstractEntity implements IMemberEntity {
responses[i] = {
value: [],
responseTime: -1,
confidence: 0,
confidence: -1,
readingConfirmation: false,
};
}
......
import * as xlsx from 'excel4node';
import * as MessageFormat from 'messageformat';
import MemberDAO from '../db/MemberDAO';
import { AbstractQuestionEntity } from '../entities/question/AbstractQuestionEntity';
import { IMemberEntity } from '../interfaces/entities/Member/IMemberEntity';
import { ILeaderBoardItemBase } from '../interfaces/leaderboard/ILeaderBoardItemBase';
import { IQuizEntity } from '../interfaces/quizzes/IQuizEntity';
......@@ -78,7 +77,7 @@ export abstract class ExcelWorksheet {
});
} else {
this._responsesWithConfidenceValue = MemberDAO.getMembersOfQuiz(this._quiz.name).filter(nickname => {
return nickname.responses.filter(responseItem => responseItem.confidence > -1);
return nickname.responses.some(responseItem => responseItem.confidence > -1);
});
}
if (this._responsesWithConfidenceValue.length > 0) {
......@@ -100,27 +99,8 @@ export abstract class ExcelWorksheet {
protected getLeaderboardData(questionIndex: number): Array<ILeaderBoardItemBase> {
const leaderBoard = new Leaderboard();
const correctResponses: any = {};
const question: AbstractQuestionEntity = this.quiz.questionList[questionIndex];
MemberDAO.getMembersOfQuiz(this._quiz.name).forEach(attendee => {
if (leaderBoard.isCorrectResponse(attendee.responses[questionIndex], question) === 1) {
if (!correctResponses[attendee.name]) {
correctResponses[attendee.name] = {
responseTime: 0,
correctQuestions: [],
confidenceValue: 0,
};
}
correctResponses[attendee.name].responseTime += <number>attendee.responses[questionIndex].responseTime;
correctResponses[attendee.name].correctQuestions.push(questionIndex);
correctResponses[attendee.name].confidenceValue += <number>attendee.responses[questionIndex].confidence;
} else {
delete correctResponses[attendee.name];
}
});
return leaderBoard.objectToArray(correctResponses);
const { correctResponses } = leaderBoard.buildLeaderboard(this.quiz);
return leaderBoard.sortBy(correctResponses, 'score');
}
private prefixNumberWithZero(num: number): string {
......
......@@ -44,7 +44,7 @@ export class FreeTextExcelWorksheet extends ExcelWorksheet implements IExcelWork
this.ws.row(1).setHeight(20);
this.ws.column(1).setWidth(this.responsesWithConfidenceValue.length > 0 ? 40 : 30);
this.ws.column(2).setWidth(30);
this.ws.column(3).setWidth(35);
this.ws.column(3).setWidth(45);
this.ws.column(4).setWidth(35);
this.ws.cell(1, 1, 1, columnsToFormat).style(defaultStyles.quizNameRowStyle);
......@@ -156,7 +156,9 @@ export class FreeTextExcelWorksheet extends ExcelWorksheet implements IExcelWork
${this.mf(answerOption.configTrimWhitespaces ? 'global.yes' : 'global.no')}`);
this.ws.cell(7, 1).string(this.mf('export.percent_correct') + ':');
const correctResponsesPercentage: number = this.leaderBoardData.length / MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
const correctResponsesPercentage: number = this.leaderBoardData.map(leaderboard => leaderboard.correctQuestions)
.filter(correctQuestions => correctQuestions.includes(this._questionIndex)).length
/ MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
this.ws.cell(7, 2).number((isNaN(correctResponsesPercentage) ? 0 : Math.round(correctResponsesPercentage)));
this.ws.cell(7, 3).string(`
......
......@@ -150,8 +150,12 @@ export class MultipleChoiceExcelWorksheet extends ExcelWorksheet implements IExc
this.ws.cell(2, 1).string(this.mf('export.question'));
this.ws.cell(6, 1).string(this.mf('export.number_of_answers') + ':');
this.ws.cell(7, 1).string(this.mf('export.percent_correct') + ':');
const correctResponsesPercentage: number = this.leaderBoardData.length / MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
const correctResponsesPercentage: number = this.leaderBoardData.map(leaderboard => leaderboard.correctQuestions)
.filter(correctQuestions => correctQuestions.includes(this._questionIndex)).length
/ MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
this.ws.cell(7, 2).number((isNaN(correctResponsesPercentage) ? 0 : Math.round(correctResponsesPercentage)));
if (this.responsesWithConfidenceValue.length > 0) {
this.ws.cell(8, 1).string(this.mf('export.average_confidence') + ':');
let confidenceSummary = 0;
......@@ -160,22 +164,30 @@ export class MultipleChoiceExcelWorksheet extends ExcelWorksheet implements IExc
});
this.ws.cell(8, 2).number(Math.round(confidenceSummary / this.responsesWithConfidenceValue.length));
}
this.ws.cell(4, 1).string(this._question.questionText.replace(/[#]*[*]*/g, ''));
for (let j = 0; j < answerList.length; j++) {
this.ws.cell(2, (j + 2)).string(this.mf('export.answer') + ' ' + (j + 1));
this.ws.cell(4, (j + 2)).string(answerList[j].answerText);
this.ws.cell(6, (j + 2)).number(calculateNumberOfAnswers(this.quiz, this._questionIndex, j));
}
let nextColumnIndex = 1;
this.ws.cell(10, nextColumnIndex++).string(this.mf('export.attendee'));
if (this._isCasRequired) {
this.ws.cell(10, nextColumnIndex++).string(this.mf('export.cas_account_id'));
this.ws.cell(10, nextColumnIndex++).string(this.mf('export.cas_account_email'));
}
this.ws.cell(10, nextColumnIndex++).string(this.mf('export.answer'));
if (this.responsesWithConfidenceValue.length > 0) {
this.ws.cell(10, nextColumnIndex++).string(this.mf('export.confidence_level'));
}
this.ws.cell(10, nextColumnIndex++).string(this.mf('export.time'));
let nextStartRow = 10;
......
......@@ -211,7 +211,9 @@ export class RangedExcelWorksheet extends ExcelWorksheet implements IExcelWorksh
this.ws.cell(6, 4).number(numberOfInputValuesPerGroup.maxRange);
this.ws.cell(7, 1).string(this.mf('export.percent_correct') + ':');
const correctResponsesPercentage: number = this.leaderBoardData.length / MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
const correctResponsesPercentage: number = this.leaderBoardData.map(leaderboard => leaderboard.correctQuestions)
.filter(correctQuestions => correctQuestions.includes(this._questionIndex)).length
/ MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
this.ws.cell(7, 2).number((isNaN(correctResponsesPercentage) ? 0 : Math.round(correctResponsesPercentage)));
if (this.responsesWithConfidenceValue.length > 0) {
......
......@@ -167,7 +167,9 @@ export class SingleChoiceExcelWorksheet extends ExcelWorksheet implements IExcel
this.ws.cell(6, 1).string(this.mf('export.number_of_answers') + ':');
this.ws.cell(7, 1).string(this.mf('export.percent_correct') + ':');
const correctResponsesPercentage: number = this.leaderBoardData.length / MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
const correctResponsesPercentage: number = this.leaderBoardData.map(leaderboard => leaderboard.correctQuestions)
.filter(correctQuestions => correctQuestions.includes(this._questionIndex)).length
/ MemberDAO.getMembersOfQuiz(this.quiz.name).length * 100;
this.ws.cell(7, 2).number((isNaN(correctResponsesPercentage) ? 0 : Math.round(correctResponsesPercentage)));
if (this.responsesWithConfidenceValue.length > 0) {
......
......@@ -3,8 +3,6 @@ import MemberDAO from '../db/MemberDAO';
import { QuestionType } from '../enums/QuestionType';
import { IMemberEntity } from '../interfaces/entities/Member/IMemberEntity';
import { IExcelWorksheet } from '../interfaces/iExcel';
import { ILeaderBoardItemBase } from '../interfaces/leaderboard/ILeaderBoardItemBase';
import { Leaderboard } from '../lib/leaderboard/leaderboard';
import { staticStatistics } from '../statistics';
import { ExcelWorksheet } from './ExcelWorksheet';
......@@ -73,43 +71,52 @@ export class SummaryExcelWorksheet extends ExcelWorksheet implements IExcelWorks
this.ws.cell(1, 1, 2, 1).style({
alignment: {
indent: 7,
indent: 5,
},
});
this.ws.cell(4, 1, 8, this.columnsToFormat).style(defaultStyles.statisticsRowStyle);
this.ws.cell(4, 3, 8, 3).style({
let currentRowIndex = 7;
if (this.quiz.sessionConfig.confidenceSliderEnabled) {
currentRowIndex++;
}
this.ws.cell(4, 1, currentRowIndex, this.columnsToFormat).style(defaultStyles.statisticsRowStyle);
this.ws.cell(4, 3, currentRowIndex, 3).style({
alignment: {
horizontal: 'left',
},
});
this.ws.cell(8, 3).style({
if (this.quiz.sessionConfig.confidenceSliderEnabled) {
}
this.ws.cell(this.quiz.sessionConfig.confidenceSliderEnabled ? currentRowIndex - 1 : currentRowIndex, 3, currentRowIndex, 3).style({
numberFormat: '#,##0',
});
this.ws.cell(10, 1, 11, this.columnsToFormat).style(defaultStyles.attendeeHeaderGroupRowStyle);
this.ws.cell(12, 1, 12, this.columnsToFormat).style(defaultStyles.attendeeHeaderRowStyle);
this.ws.cell(12, 1).style({
currentRowIndex += 2;
this.ws.cell(currentRowIndex, 1, currentRowIndex, this.columnsToFormat).style(defaultStyles.attendeeHeaderGroupRowStyle);
this.ws.cell(++currentRowIndex, 1, currentRowIndex, this.columnsToFormat).style(defaultStyles.attendeeHeaderRowStyle);
this.ws.cell(currentRowIndex, 1).style({
alignment: {
horizontal: 'left',
},
});
this.ws.row(12).filter({
firstRow: 12,
this.ws.row(currentRowIndex).filter({
firstRow: currentRowIndex,
firstColumn: 1,
lastRow: 12,
lastColumn: this.columnsToFormat,
lastRow: currentRowIndex,
lastColumn: this.columnsToFormat - 1,
});
let nextStartRow = 18;
let dataWithoutCompleteCorrectQuestions = 0;
this.leaderBoardData.forEach((leaderboardItem, indexInList) => {
let hasNotAllQuestionsCorrect = false;
this.quiz.questionList.forEach((item, index) => {
if ([
QuestionType.SurveyQuestion, QuestionType.ABCDSingleChoiceQuestion,
].includes(item.TYPE) && leaderboardItem.correctQuestions.indexOf((index)) === -1) {
if (![
QuestionType.SurveyQuestion, QuestionType.ABCDSingleChoiceQuestion,
].includes(item.TYPE) && leaderboardItem.correctQuestions.indexOf((index)) === -1) {
hasNotAllQuestionsCorrect = true;
}
});
......@@ -117,57 +124,59 @@ export class SummaryExcelWorksheet extends ExcelWorksheet implements IExcelWorks
dataWithoutCompleteCorrectQuestions++;
return;
}
let nextColumnIndex = 3;
nextStartRow++;
const targetRow = indexInList + 13;
let nextColumnIndex = 2;
currentRowIndex++;
if (this.quiz.sessionConfig.confidenceSliderEnabled) {
this.ws.cell(targetRow, nextColumnIndex++).style({
this.ws.cell(currentRowIndex, nextColumnIndex++).style({
alignment: {
horizontal: 'center',
},
});
}
this.ws.cell(targetRow, nextColumnIndex++).style({
this.ws.cell(currentRowIndex, nextColumnIndex++).style({
alignment: {
horizontal: 'center',
},
numberFormat: '#,##0;',
});
this.ws.cell(targetRow, nextColumnIndex).style({
this.ws.cell(currentRowIndex, nextColumnIndex).style({
alignment: {
horizontal: 'center',
},
numberFormat: '#,##0;',
});
});
if (nextStartRow === 18) {
this.ws.cell(13, 1, 13, this.columnsToFormat, true).style(Object.assign({}, defaultStyles.attendeeEntryRowStyle, {
if (dataWithoutCompleteCorrectQuestions === this.leaderBoardData.length) {
this.ws.cell(++currentRowIndex, 1, currentRowIndex, this.columnsToFormat, true).style(Object.assign({}, defaultStyles.attendeeEntryRowStyle, {
alignment: {
horizontal: 'center',
},
}));
nextStartRow++;
} else {
this.ws.cell(13, 1, (this.leaderBoardData.length + 12 - dataWithoutCompleteCorrectQuestions), this.columnsToFormat)
this.ws.cell(currentRowIndex, 1, (this.leaderBoardData.length + currentRowIndex - 1 - dataWithoutCompleteCorrectQuestions),
this.columnsToFormat)
.style(defaultStyles.attendeeEntryRowStyle);
}
currentRowIndex += 6;
this.ws.cell(nextStartRow++, 1, nextStartRow++, this.columnsToFormat).style(defaultStyles.attendeeHeaderGroupRowStyle);
this.ws.cell(currentRowIndex, 1, currentRowIndex, this.columnsToFormat).style(defaultStyles.attendeeHeaderGroupRowStyle);
currentRowIndex++;
this.ws.cell(nextStartRow, 1, nextStartRow, this.columnsToFormat).style(defaultStyles.attendeeHeaderRowStyle);
this.ws.cell(nextStartRow, 1).style({
this.ws.cell(currentRowIndex, 1, currentRowIndex, this.columnsToFormat).style(defaultStyles.attendeeHeaderRowStyle);
this.ws.cell(currentRowIndex, 1).style({
alignment: {
horizontal: 'left',
},
});
nextStartRow++;
currentRowIndex++;
this.ws.cell(nextStartRow, 1, (this.leaderBoardData.length + (nextStartRow - 1)), this.columnsToFormat)
this.ws.cell(currentRowIndex, 1, (this.leaderBoardData.length + (currentRowIndex - 1)), this.columnsToFormat)
.style(defaultStyles.attendeeEntryRowStyle);
this.leaderBoardData.forEach((leaderboardItem, indexInList) => {
let nextColumnIndex = 3;
const targetRow = indexInList + nextStartRow;
const targetRow = indexInList + currentRowIndex;
if (this.quiz.sessionConfig.confidenceSliderEnabled) {
this.ws.cell(targetRow, nextColumnIndex++).style({
alignment: {
......@@ -252,16 +261,15 @@ export class SummaryExcelWorksheet extends ExcelWorksheet implements IExcelWorks
let nextColumnIndex = 1;
this.ws.cell(currentRowIndex, nextColumnIndex).string(this.mf('export.attendee_complete_correct'));
currentRowIndex += 2;
currentRowIndex += 1;
this.ws.cell(currentRowIndex, nextColumnIndex++).string(this.mf('export.attendee'));
if (this._isCasRequired) {
this.ws.cell(currentRowIndex, nextColumnIndex++).string(this.mf('export.cas_account_id'));
this.ws.cell(currentRowIndex, nextColumnIndex++).string(this.mf('export.cas_account_email'));
}
this.ws.cell(currentRowIndex, nextColumnIndex++).string(this.mf('export.correct_questions'));
if