Skip to content
Merged

0.7.1 #159

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ export class WorkspaceFilesController {
data: { type: 'array', items: { $ref: '#/components/schemas/FilesDto' } },
total: { type: 'number' },
page: { type: 'number' },
limit: { type: 'number' }
limit: { type: 'number' },
fileTypes: { type: 'array', items: { type: 'string' } }
}
}
})
Expand All @@ -91,21 +92,22 @@ export class WorkspaceFilesController {
@Query('fileType') fileType?: string,
@Query('fileSize') fileSize?: string,
@Query('searchText') searchText?: string
): Promise<{ data: FilesDto[]; total: number; page: number; limit: number }> {
): Promise<{ data: FilesDto[]; total: number; page: number; limit: number; fileTypes: string[] }> {
if (!workspace_id || workspace_id <= 0) {
throw new BadRequestException(
'Invalid workspace ID. Please provide a valid ID.'
);
}
try {
const [files, total] = await this.workspaceFilesService.findFiles(workspace_id, {
const [files, total, fileTypes] = await this.workspaceFilesService.findFiles(workspace_id, {
page, limit, fileType, fileSize, searchText
});
return {
data: files,
total,
page,
limit
limit,
fileTypes
};
} catch (error) {
throw new BadRequestException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
} from 'typeorm';

@Entity()
@Unique('file_upload_id', ['file_id'])
@Unique('file_upload_id', ['file_id', 'workspace_id'])
class FileUpload {
@PrimaryColumn({ type: 'integer' })
id: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Unit } from '../entities/unit.entity';
import { Booklet } from '../entities/booklet.entity';
import { ResponseEntity } from '../entities/response.entity';
import { CodingStatistics } from './shared-types';
import { prepareDefinition } from '../../utils/voud/transform';
import { extractVariableLocation } from '../../utils/voud/extractVariableLocation';

@Injectable()
Expand Down Expand Up @@ -350,7 +349,7 @@ export class WorkspaceCodingService {
const bookletInfo = booklet?.bookletinfo;
const loginName = person?.login || '';
const loginCode = person?.code || '';
const loginGroup = person.group || '';
//const loginGroup = person.group || '';
const bookletId = bookletInfo?.name || '';
const unitKey = unit?.name || '';
const unitAlias = unit?.alias || '';
Expand Down Expand Up @@ -386,7 +385,7 @@ export class WorkspaceCodingService {
this.logger.warn(`VOUD file not found for unit ${unitKey}`);
}

const url = `${server}/#/replay/${loginName}@${loginCode}@${loginGroup}/${unitKey}/${variablePage}/${variableAnchor}?auth=${authToken}`;
const url = `${server}/#/replay/${loginName}@${loginCode}@${bookletId}/${unitKey}/${variablePage}/${variableAnchor}?auth=${authToken}`;

return {
unit_key: unitKey,
Expand Down Expand Up @@ -431,7 +430,7 @@ export class WorkspaceCodingService {
const bookletInfo = booklet?.bookletinfo;
const loginName = person?.login || '';
const loginCode = person?.code || '';
const loginGroup = person.group || '';
// const loginGroup = person.group || '';
const bookletId = bookletInfo?.name || '';
const unitKey = unit?.name || '';
const unitAlias = unit?.alias || '';
Expand Down Expand Up @@ -468,7 +467,7 @@ export class WorkspaceCodingService {
this.logger.warn(`VOUD file not found for unit ${unitKey}`);
}

const url = `${server}/#/replay/${loginName}@${loginCode}@${loginGroup}/${unitKey}/${variablePage}/${variableAnchor}?auth=${authToken}`;
const url = `${server}/#/replay/${loginName}@${loginCode}@${bookletId}/${unitKey}/${variablePage}/${variableAnchor}?auth=${authToken}`;
return {
unit_key: unitKey,
unit_alias: unitAlias,
Expand Down
46 changes: 28 additions & 18 deletions apps/backend/src/app/database/services/workspace-files.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,28 @@ export class WorkspaceFilesService {
private fileUploadRepository: Repository<FileUpload>
) {}

async findAllFileTypes(workspaceId: number): Promise<string[]> {
this.logger.log(`Fetching all file types for workspace: ${workspaceId}`);

try {
const result = await this.fileUploadRepository
.createQueryBuilder('file')
.select('DISTINCT file.file_type', 'file_type')
.where('file.workspace_id = :workspaceId', { workspaceId })
.andWhere('file.file_type IS NOT NULL')
.getRawMany();

return result.map(item => item.file_type).sort();
} catch (error) {
this.logger.error(`Error fetching file types for workspace ${workspaceId}: ${error.message}`, error.stack);
return [];
}
}

async findFiles(
workspaceId: number,
options?: { page: number; limit: number; fileType?: string; fileSize?: string; searchText?: string }
): Promise<[FilesDto[], number]> {
): Promise<[FilesDto[], number, string[]]> {
this.logger.log(`Fetching test files for workspace: ${workspaceId}`);
const {
page = 1, limit = 20, fileType, fileSize, searchText
Expand All @@ -89,6 +107,7 @@ export class WorkspaceFilesService {
// fileSize-Filter: z.B. '0-10KB', '10KB-100KB', '100KB-1MB', '1MB-10MB', '10MB+'
const KB = 1024;
const MB = 1024 * KB;
// eslint-disable-next-line default-case
switch (fileSize) {
case '0-10KB':
qb = qb.andWhere('file.file_size < :max', { max: 10 * KB });
Expand Down Expand Up @@ -123,7 +142,11 @@ export class WorkspaceFilesService {

const [files, total] = await qb.getManyAndCount();
this.logger.log(`Found ${files.length} files (page ${validPage}, limit ${validLimit}, total ${total}).`);
return [files, total];

// Get all file types for this workspace
const fileTypes = await this.findAllFileTypes(workspaceId);

return [files, total, fileTypes];
}

async deleteTestFiles(workspace_id: number, fileIds: string[]): Promise<boolean> {
Expand Down Expand Up @@ -468,7 +491,7 @@ export class WorkspaceFilesService {
file_size: file.size,
data: file.buffer.toString(),
file_id: resolvedFileId
}, ['file_id']);
}, ['file_id', 'workspace_id']);
} catch (error) {
this.logger.error(`Error processing XML file: ${error.message}`);
throw error;
Expand All @@ -485,7 +508,7 @@ export class WorkspaceFilesService {
file_size: file.size,
file_id: resourceFileId,
data: file.buffer.toString()
}, ['file_id']);
}, ['file_id', 'workspace_id']);
}

private async handleOctetStreamFile(workspaceId: number, file: FileIo): Promise<void> {
Expand Down Expand Up @@ -800,7 +823,7 @@ export class WorkspaceFilesService {
async testCenterImport(entries: Record<string, unknown>[]): Promise<boolean> {
try {
const registry = this.fileUploadRepository.create(entries);
await this.fileUploadRepository.upsert(registry, ['file_id']);
await this.fileUploadRepository.upsert(registry, ['file_id', 'workspace_id']);
return true;
} catch (error) {
this.logger.error('Error during test center import', error);
Expand All @@ -811,20 +834,9 @@ export class WorkspaceFilesService {
private static getPlayerId(file: FileIo): string {
try {
const playerCode = file.buffer.toString();

const playerContent = cheerio.load(playerCode);

// Search for JSON+LD <script> tags in the parsed DOM.
const metaDataElement = playerContent('script[type="application/ld+json"]');
if (!metaDataElement.length) {
console.error('Meta-data <script> tag not found');
}

const metadata = JSON.parse(metaDataElement.text());
if (!metadata.id || !metadata.version) {
console.error('Invalid metadata structure: Missing id or version');
}

return WorkspaceFilesService.normalizePlayerId(`${metadata.id}-${metadata.version}`);
} catch (error) {
return WorkspaceFilesService.getResourceId(file);
Expand Down Expand Up @@ -871,7 +883,6 @@ export class WorkspaceFilesService {
*/
async getUnitContent(workspaceId: number, unitId: number): Promise<string> {
try {
console.log(`Retrieving unit content for workspace ${workspaceId} and unit ${unitId}`);
const unitFile = await this.fileUploadRepository.findOne({
where: { workspace_id: workspaceId, file_id: `${unitId}` }
});
Expand Down Expand Up @@ -925,7 +936,6 @@ export class WorkspaceFilesService {
*/
async getCodingSchemeByRef(workspaceId: number, codingSchemeRef: string): Promise<FileDownloadDto | null> {
try {
console.log(`Retrieving coding scheme for workspace ${workspaceId} with reference ${codingSchemeRef}`);
const codingSchemeFile = await this.fileUploadRepository.findOne({
where: {
workspace_id: workspaceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ export class WorkspaceTestResultsService {
const validLimit = Math.min(Math.max(1, limit), MAX_LIMIT); // Between 1 and MAX_LIMIT

try {
// Use query builder to support text search
const queryBuilder = this.personsRepository.createQueryBuilder('person')
.where('person.workspace_id = :workspace_id', { workspace_id })
.select([
Expand Down Expand Up @@ -250,31 +249,44 @@ export class WorkspaceTestResultsService {
}

async findUnitResponse(workspaceId: number, connector: string, unitId: string): Promise<{ responses: { id: string, content: { id: string; value: string; status: string }[] }[] }> {
const [login, code, group] = connector.split('@');
const [login, code, bookletId] = connector.split('@');
const person = await this.personsRepository.findOne({
where: {
code, login, group, workspace_id: workspaceId
code, login, workspace_id: workspaceId
}
});
if (!person) {
throw new Error(`Person mit ID ${person.id} wurde nicht gefunden.`);
}

const booklets = await this.bookletRepository.find({
where: { personid: person.id }
const bookletInfo = await this.bookletInfoRepository.findOne({
where: { name: bookletId }
});

if (!booklets || booklets.length === 0) {
throw new Error(`Keine Booklets für die Person mit ID ${person.id} gefunden.`);
if (!bookletInfo) {
throw new Error(`Kein Booklet mit der ID ${bookletId} gefunden.`);
}

const booklet = await this.bookletRepository.findOne({
where: {
personid: person.id,
infoid: bookletInfo.id
}
});

if (!booklet) {
throw new Error(`Kein Booklet für die Person mit ID ${person.id} und Booklet ID ${bookletId} gefunden.`);
}

const booklets = [booklet];
const unit = await this.unitRepository.findOne({
where: {
bookletid: In(booklets.map(booklet => booklet.id)),
bookletid: In(booklets.map(b => b.id)),
alias: unitId
},
relations: ['responses']
});
const mappedResponses = unit.responses// Filter für subform = 'elementCodes'
const mappedResponses = unit.responses
.filter(response => response.subform === 'elementCodes')
.map(response => ({
id: response.variableid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class CodingManagementComponent implements AfterViewInit, OnInit, OnDestr
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatPaginator) paginator!: MatPaginator;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any[] = [];
dataSource = new MatTableDataSource<CodingListItem>(this.data);
displayedColumns: string[] = ['unitname', 'variableid', 'value', 'codedstatus', 'actions'];
Expand Down Expand Up @@ -223,7 +224,6 @@ export class CodingManagementComponent implements AfterViewInit, OnInit, OnDestr
})
)
.subscribe(response => {
console.log(response);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.data = response.data.map((item: any) => ({
id: item.id,
Expand Down Expand Up @@ -314,7 +314,7 @@ export class CodingManagementComponent implements AfterViewInit, OnInit, OnDestr
if (!token) {
return;
}
const url = `${window.location.origin}/#/replay/${response.login_name}@${response.login_code}@${response.login_group}/${response.unitname}/${page}/${response.variableid}?auth=${token}`;
const url = `${window.location.origin}/#/replay/${response.login_name}@${response.login_code}@${response.booklet_id}/${response.unitname}/${page}/${response.variableid}?auth=${token}`;
window.open(url, '_blank');
}
);
Expand Down Expand Up @@ -572,7 +572,6 @@ export class CodingManagementComponent implements AfterViewInit, OnInit, OnDestr
)
.subscribe(xmlContent => {
if (!xmlContent) return;
console.log(xmlContent);
const codingSchemeRef = this.extractCodingSchemeRefFromXml(xmlContent);

if (codingSchemeRef) {
Expand All @@ -595,7 +594,10 @@ export class CodingManagementComponent implements AfterViewInit, OnInit, OnDestr
return codingSchemeRefElement.textContent.trim();
}
} catch (error) {
console.error('Fehler beim Parsen des XML:', error);
this.snackBar.open('Fehler beim Verarbeiten der Unit-XML-Daten', 'Schließen', {
duration: 5000,
panelClass: ['error-snackbar']
});
}

return null;
Expand Down Expand Up @@ -641,7 +643,6 @@ export class CodingManagementComponent implements AfterViewInit, OnInit, OnDestr
});
}
});

}

showUnitXml(unitId: number): void {
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/app/components/home/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[appTitle]="'Web application for coding'"
[introHtml]="'appService.appConfig.introHtml'"
[appName]="'IQB-Kodierbox'"
[appVersion]="'0.7.0'"
[appVersion]="'0.7.1'"
[userName]="authData.userName"
[userLongName]="appService.userProfile.firstName + ' ' + appService.userProfile.lastName"
[isUserLoggedIn]="Number(authData.userId) > 0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,11 @@ export class UnitPlayerComponent implements AfterViewInit, OnChanges, OnDestroy
const unitPlayerChange = changes[unitPlayer];
const unitResponsesChange = changes[unitResponses];

// Check if `unitDef` is set to undefined
if (unitDefChange?.previousValue && !unitDefChange.currentValue) {
this.resetIframeContent();
return;
}

// Check if `unitDef` has changed
if (unitDefChange?.currentValue && unitDefChange.previousValue !== unitDefChange.currentValue) {
this.handleUnitDefChange(unitDefChange.currentValue, unitPlayerChange, unitResponsesChange);
}
Expand Down Expand Up @@ -104,7 +102,12 @@ export class UnitPlayerComponent implements AfterViewInit, OnChanges, OnDestroy
if (unitResponsesChange?.currentValue?.responses) {
this.dataParts = unitResponsesChange.currentValue.responses.reduce(
(acc: { [key: string]: string }, response: { id: string; content: string }) => {
acc[response.id] = JSON.stringify(response.content);
try {
JSON.parse(response.content);
acc[response.id] = response.content;
} catch (e) {
acc[response.id] = JSON.stringify(response.content);
}
return acc;
}, {}
);
Expand Down Expand Up @@ -181,9 +184,8 @@ export class UnitPlayerComponent implements AfterViewInit, OnChanges, OnDestroy
msgData.specVersion?.match(/\d+/);
this.playerApiVersion = majorVersionMatch && majorVersionMatch.length > 0 ?
Number(majorVersionMatch[0]) :
2; // Default to version 2 if none found
2;
} else {
// Default version for non-relevant message types
this.playerApiVersion = 1;
}

Expand Down Expand Up @@ -264,8 +266,6 @@ export class UnitPlayerComponent implements AfterViewInit, OnChanges, OnDestroy
break;

default:
// eslint-disable-next-line no-console
console.warn(`processMessagePost ignored message: ${msgType}`);
break;
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/app/services/backend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,15 +484,15 @@ export class BackendService {
fileType?: string,
fileSize?: string,
searchText?: string
): Observable<PaginatedResponse<FilesInListDto>> {
): Observable<PaginatedResponse<FilesInListDto> & { fileTypes: string[] }> {
let params = new HttpParams()
.set('page', page.toString())
.set('limit', limit.toString());
if (fileType) params = params.set('fileType', fileType);
if (fileSize) params = params.set('fileSize', fileSize);
if (searchText) params = params.set('searchText', searchText);

return this.http.get<PaginatedResponse<FilesInListDto>>(
return this.http.get<PaginatedResponse<FilesInListDto> & { fileTypes: string[] }>(
`${this.serverUrl}admin/workspace/${workspaceId}/files`,
{ headers: this.authHeader, params }
);
Expand Down
Loading