Skip to content

Commit

Permalink
EVDOC01-163: Afegir missatge i opció de mostrar informes d'assistènci…
Browse files Browse the repository at this point in the history
…a (CSV) (#39)
  • Loading branch information
Aniii10 authored Oct 29, 2024
1 parent b391d3a commit 9326b20
Show file tree
Hide file tree
Showing 20 changed files with 417 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public List<Meeting> getAllMeetingsFromSite(String siteId) {
return meetingRepository.getSiteMeetings(siteId);
}

public List<Meeting> getUserMeetings(String userId, String siteId, List <String> groupIds) {
public List<Meeting> getUserMeetings(String userId, String siteId, List<String> groupIds) {
return meetingRepository.getMeetings(userId, siteId, groupIds);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.meetings.api.MeetingService;
import org.sakaiproject.meetings.api.model.AttendeeType;
import org.sakaiproject.meetings.api.model.Meeting;
import org.sakaiproject.meetings.api.model.MeetingAttendee;
import org.sakaiproject.meetings.api.model.*;
import org.sakaiproject.meetings.controller.data.GroupData;
import org.sakaiproject.meetings.controller.data.MeetingData;
import org.sakaiproject.meetings.controller.data.NotificationType;
Expand All @@ -43,17 +41,18 @@
import org.sakaiproject.microsoft.api.MicrosoftCommonService;
import org.sakaiproject.microsoft.api.MicrosoftSynchronizationService;
import org.sakaiproject.microsoft.api.SakaiProxy;
import org.sakaiproject.microsoft.api.data.MeetingRecordingData;
import org.sakaiproject.microsoft.api.data.SakaiCalendarEvent;
import org.sakaiproject.microsoft.api.data.TeamsMeetingData;
import org.sakaiproject.microsoft.api.data.*;
import org.sakaiproject.microsoft.api.exceptions.MicrosoftCredentialsException;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.user.api.User;
import org.sakaiproject.util.ResourceLoader;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand Down Expand Up @@ -104,7 +103,17 @@ public class MeetingsController {
private static final String NOTIF_CONTENT = "notification.content";
private static final String SMTP_FROM = "[email protected]";
private static final String NO_REPLY = "no-reply@";

private static final String MEETING_ATTENDANCE_REPORT = rb.getString("meeting.attendance_report");
private static final String MEETING_COLUMN_NAME = rb.getString("meeting.column_name");
private static final String MEETING_COLUMN_EMAIL = rb.getString("meeting.column_email");
private static final String MEETING_COLUMN_ROL = rb.getString("meeting.column_role");
private static final String MEETING_COLUMN_DURATION = rb.getString("meeting.column_duration");
private static final String MEETING_DURATION_INTERVAL = rb.getString("meeting.interval_duration");
private static final String MEETING_ENTRY_DATE = rb.getString("meeting.entry_date");
private static final String MEETING_EXIT_DATE = rb.getString("meeting.exit_date");
private static final String MEETING_DETAILS= rb.getString("meeting.details");


/**
* Check if there's an user logged
* @return
Expand Down Expand Up @@ -593,6 +602,42 @@ public void deleteMeeting(@PathVariable String meetingId) throws MeetingsExcepti
throw new MeetingsException(e.getLocalizedMessage());
}
}

@GetMapping(value = "/meeting/{meetingId}/attendanceReport", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getMeetingAttendanceReport(@PathVariable String meetingId, @RequestParam(required = false) String format) throws MeetingsException {
checkCurrentUserInMeeting(meetingId);
Meeting meeting = meetingService.getMeeting(meetingId);
String onlineMeetingId = meetingService.getMeetingProperty(meeting, ONLINE_MEETING_ID);
String organizerEmail = meetingService.getMeetingProperty(meeting, ORGANIZER_USER);
checkUpdatePermissions(meeting.getSiteId());
microsoftCommonService.inicializeMeetingNameColumns(MEETING_ATTENDANCE_REPORT, MEETING_COLUMN_NAME, MEETING_COLUMN_EMAIL, MEETING_COLUMN_ROL, MEETING_COLUMN_DURATION, MEETING_DURATION_INTERVAL, MEETING_ENTRY_DATE, MEETING_EXIT_DATE, MEETING_DETAILS);

try {
List<AttendanceRecord> attendanceRecords = microsoftCommonService.getMeetingAttendanceReport(onlineMeetingId, organizerEmail);
if ("pdf".equalsIgnoreCase(format)) {
String filename = "attendance_report.pdf";
ContentDisposition contentDisposition = ContentDisposition.builder("attachment").filename(filename).build();

byte[] pdfContent = microsoftCommonService.createAttendanceReportPdf(attendanceRecords);

return ResponseEntity.ok()
.headers(h -> h.setContentDisposition(contentDisposition))
.contentType(MediaType.APPLICATION_PDF)
.body(pdfContent);
} else if ("csv".equalsIgnoreCase(format)) {
byte[] csvContent = microsoftCommonService.createAttendanceReportCsv(attendanceRecords);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"attendance_report.csv\"")
.contentType(MediaType.TEXT_PLAIN)
.body(csvContent);
} else {
return ResponseEntity.ok(attendanceRecords);
}
} catch (Exception e) {
log.error("Error al obtener el reporte de asistencia", e);
throw new MeetingsException(e.getLocalizedMessage());
}
}

/**
* Get i18n bundle
Expand Down
11 changes: 11 additions & 0 deletions meetings/tool/src/main/resources/Messages.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
notification.subject=A new meeting \u0027{0}\u0027 has been published in the site \u0027{1}\u0027
notification.content=You have been invited to participate in the meeting <i>{0}</i>.

#Meetings
meeting.attendance_report=Attendance Report
meeting.column_name=Name
meeting.column_email=Email
meeting.column_role=Role
meeting.column_duration=Duration (seconds)
meeting.entry_date=Entry Date
meeting.exit_date=Exit Date
meeting.interval_duration=Interval Duration (seconds)
meeting.details=Assistance Details for
13 changes: 12 additions & 1 deletion meetings/tool/src/main/resources/Messages_ca.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
notification.subject=S\u2019ha publicat una nova reuni\u00F3 \u0027{0}\u0027 a l\u2019espai {1}
notification.content=Ha sigut convidat a participar en la reuni\u00F3 <i>{0}</i>.
notification.content=Ha sigut convidat a participar en la reuni\u00F3 <i>{0}</i>.

#Meetings
meeting.attendance_report=Report d\u2019assitencia
meeting.column_name=Nom
meeting.column_email=Correu
meeting.column_role=Rol
meeting.column_duration=Duraci\u00F3 (segons)
meeting.entry_date=Data d\u2019entrada
meeting.exit_date=Data d\u2019eixida
meeting.interval_duration=Interval Duration (seconds)
meeting.details=Detalls d\u2019assist\u00e8ncia per a
11 changes: 11 additions & 0 deletions meetings/tool/src/main/resources/Messages_es.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
notification.subject=Se ha publicado una nueva reuni\u00F3n \u0027{0}\u0027 en el sitio {1}
notification.content=Ha sido invitado a participar en la reuni\u00F3n <i>{0}</i>.

#Meetings
meeting.attendance_report=Reporte de assistencia
meeting.column_name=Nombre
meeting.column_email=Email
meeting.column_role=Rol
meeting.column_duration=Duraci\u00f3n (segundos)
meeting.entry_date=Fecha de entrada
meeting.exit_date=Fecha de salida
meeting.interval_duration=Intervalo de Duraci\u00f3n (segundos)
meeting.details=Detalles de asistencia para
4 changes: 4 additions & 0 deletions meetings/tool/src/main/resources/card.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ status_text_unknown=unknown status
status_text_waiting=waiting for start
edit_action=Edit
delete_action=Delete
download_attendance_report_action=Download attendance report
download_report_pdf= Download in pdf
download_report_excel= Download in csv
preview_report= Preview report
get_link_action=Get Link
message_link_copied=Link copied to clipboard
check_recordings_action=Check recordings
Expand Down
4 changes: 4 additions & 0 deletions meetings/tool/src/main/resources/card_ca.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ status_text_live=en directe
status_text_starts=comen\u00E7a
status_text_unknown=estat descononegut
status_text_waiting=esperant per a comen\u00E7ar
download_attendance_report_action=Descarregar informes d\u2019assist\u00e8ncia
download_report_pdf= Descarregar en pdf
download_report_excel= Descarregar en csv
preview_report= Previsualitzar els informes
edit_action=Edita
delete_action=Elimina
get_link_action=Obtindre un enlla\u00E7
Expand Down
4 changes: 4 additions & 0 deletions meetings/tool/src/main/resources/card_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ status_text_unknown=estado desconocido
status_text_waiting=esperando para comenzar
edit_action=Editar
delete_action=Eliminar
download_attendance_report_action=Descargar informes de assistencia
download_report_pdf= Descargar en pdf
download_report_excel= Descargar en csv
preview_report= Previsualizar los informes
get_link_action=Obtener Enlace
message_link_copied=Enlace copiado al portapapeles
check_recordings_action=Comprobar grabaciones
Expand Down
1 change: 1 addition & 0 deletions meetings/tool/src/main/resources/main.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ past=Past
search_results=Search results
search=Search for meetings
today=Today
meeting_alert=For the correct functioning of the tool, it is recommended to log in to Microsoft with Udl credentials.
1 change: 1 addition & 0 deletions meetings/tool/src/main/resources/main_ca.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ past=Anteriors
search_results=Resultats de la cerca
search=Cerca de reunions
today=Avui
meeting_alert=Pel correcte funcionament de l\u2019eina es recomana iniciar sessi\u00F3 a Microsoft amb les credencials UdL.

1 change: 1 addition & 0 deletions meetings/tool/src/main/resources/main_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ past=Anteriores
search_results=Resultados de b\u00fasqueda
search=B\u00fasqueda de reuniones
today=Hoy
meeting_alert=Para el correcto funcionamiento de la herramienta se recomienda iniciar sesi\u00f3n a Microsoft con las credenciales Udl.
31 changes: 31 additions & 0 deletions meetings/ui/src/main/frontend/src/components/sakai-dropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@
/>
{{ item.string }}
</a>
<ul v-if="item.subMenu" role="menu" class="dropdown-submenu">
<li v-for="subItem in item.subMenu" :key="subItem.string" class="divider">
<a
v-if="subItem.show"
class="dropdown-item"
@click="handleClick(subItem, $event)"
>
<sakai-icon :iconkey="subItem.icon" class="icon-wrap" :class="item.icon"/>
{{ subItem.string }}
</a>
</li>
</ul>
</li>
</ul>
</div>
Expand Down Expand Up @@ -141,5 +153,24 @@ export default {
border-bottom: none;
}
}
.dropdown-submenu {
box-shadow: var(--elevation-1dp);
border: 1px solid var(--button-border-color);
background-color: var(--tool-menu-background-color);
position: absolute;
padding: 0;
left: 100%;
top: 100px;
display: none;
border-radius: 10px;
list-style: none;
}
li:hover > .dropdown-submenu {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,12 @@ export default {
{ "string": this.i18n.edit_action, "icon": "edit", "action": this.editMeeting, "show": this.editable },
{ "string": this.i18n.get_link_action, "icon": "link", "action": this.getMeetingLink, "url": this.url, "show": this.editable && this.showJoinButton },
{ "string": this.i18n.check_recordings_action, "icon": "videocamera", "action": this.checkMeetingRecordings, "show": true },
{ "string": this.i18n.delete_action, "icon": "delete", "action": this.askDeleteMeeting, "show": this.editable}
{ "string": this.i18n.delete_action, "icon": "delete", "action": this.askDeleteMeeting, "show": this.editable},
{ "string": this.i18n.download_attendance_report_action, "icon": "download", "show": true,
"subMenu": [ { "string": this.i18n.download_report_pdf, "icon": "filePdf", "action": () => this.downloadAttendanceReport('pdf'), "show": false },
{ "string": this.i18n.download_report_excel, "icon": "fileCsv", "action": () => this.downloadAttendanceReport('csv'), "show": true },
{ "string": this.i18n.preview_report, "icon": "eye", "action": this.downloadAttendanceReport, "show": false }
]}
];
},
showJoinButton() {
Expand Down Expand Up @@ -350,6 +355,30 @@ export default {
.catch((error) => console.error('Error:', error))
.then((response) => this.$emit('onDeleted', this.id));
},
downloadAttendanceReport(format) {
fetch(`${constants.toolPlacement}/meeting/${this.id}/attendanceReport?format=${format}`, {
credentials: 'include',
method: 'GET',
cache: "no-cache",
headers: { "Content-Type": "application/json; charset=utf-8" },
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.blob();
})
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `attendance_report.${format}`;
document.body.appendChild(a);
a.click();
a.remove();
})
.catch(error => console.error('Error downloading report:', error));
},
editMeeting() {
let parameters = {
id: this.id,
Expand Down
6 changes: 5 additions & 1 deletion meetings/ui/src/main/frontend/src/resources/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ const iconsFontawsome = {
presentation: "fa-desktop",
remove: "fa-trash",
refresh: "fa-refresh",
spinner: "fa-spinner fa-spin"
spinner: "fa-spinner fa-spin",
download: "fa fa-download",
filePdf: "fa fa-file-pdf-o",
fileCsv: "fa fa-file",
eye: "fa fa-eye",
};
const iconsBootstrap = {
};
Expand Down
1 change: 1 addition & 0 deletions meetings/ui/src/main/frontend/src/views/Main.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
</SakaiDropdownButton>
-->
</div>
<div style="bottom: 10px;" class="sak-banner-info mt-0 mb-0">{{ i18n.meeting_alert }}</div>
<div v-if="searching && meetingsList.length > 0">
<div class="section-heading">
<h1 id="flush-headingOne" class="h4">{{ i18n.search_results }}</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.sakaiproject.microsoft.api.data.MicrosoftDriveItemFilter;
import org.sakaiproject.microsoft.api.data.MicrosoftMembersCollection;
import org.sakaiproject.microsoft.api.data.MicrosoftTeam;
import org.sakaiproject.microsoft.api.data.AttendanceRecord;
import org.sakaiproject.microsoft.api.data.MicrosoftUser;
import org.sakaiproject.microsoft.api.data.MicrosoftUserIdentifier;
import org.sakaiproject.microsoft.api.data.SynchronizationStatus;
Expand Down Expand Up @@ -155,7 +156,11 @@ public static enum PermissionRoles { READ, WRITE }
TeamsMeetingData createOnlineMeeting(String userEmail, String subject, Instant startDate, Instant endDate, List<String> coorganizerEmails) throws MicrosoftCredentialsException;
void updateOnlineMeeting(String userEmail, String meetingId, String subject, Instant startDate, Instant endDate, List<String> coorganizerEmails) throws MicrosoftCredentialsException;
List<MeetingRecordingData> getOnlineMeetingRecordings(String onlineMeetingId, List<String> teamIdsList, boolean force) throws MicrosoftCredentialsException;

List<AttendanceRecord> getMeetingAttendanceReport(String onlineMeetingId, String userEmail) throws MicrosoftCredentialsException;
void inicializeMeetingNameColumns(String meetingAttendanceReport, String meetingName, String meetingEmail, String meetingRole, String meetingDuration, String meetingDurationInterval, String meetingEntryDate, String meetingExitDate, String meetingsDetails);
byte[] createAttendanceReportPdf(List<AttendanceRecord> attendanceRecords);
byte[] createAttendanceReportCsv(List<AttendanceRecord> attendanceRecords);

// ---------------------------------------- ONE-DRIVE (APPLICATION) --------------------------------------------------------
List<MicrosoftDriveItem> getGroupDriveItems(String groupId) throws MicrosoftCredentialsException;
List<MicrosoftDriveItem> getGroupDriveItems(String groupId, List<String> channelIds) throws MicrosoftCredentialsException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.sakaiproject.microsoft.api.data;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.time.Instant;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class AttendanceInterval {
public String joinDateTime;
public String leaveDateTime;
public int durationInSeconds;

public String getJoinDateTime() { return joinDateTime; }
public void setJoinDateTime(String joinDateTime) { this.joinDateTime = joinDateTime; }

public String getLeaveDateTime() { return leaveDateTime; }
public void setLeaveDateTime(String leaveDateTime) { this.leaveDateTime = leaveDateTime; }

public int getDurationInSeconds() { return durationInSeconds; }
public void setDurationInSeconds(int durationInSeconds) { this.durationInSeconds = durationInSeconds; }
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.sakaiproject.microsoft.api.data;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.List;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class AttendanceRecord {

private String email;
public String id;
public String displayName;
private String role;
private int totalAttendanceInSeconds;
private List<AttendanceInterval> attendanceIntervals;

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }

public String getId() { return id; }
public void setId(String id) { this.id = id; }

public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }

public String getRole() { return role; }
public void setRole(String role) { this.role = role; }

public int getTotalAttendanceInSeconds() { return totalAttendanceInSeconds; }
public void setTotalAttendanceInSeconds(int totalAttendanceInSeconds) { this.totalAttendanceInSeconds = totalAttendanceInSeconds; }

public List<AttendanceInterval> getAttendanceIntervals() { return attendanceIntervals; }
public void setAttendanceIntervals(List<AttendanceInterval> attendanceIntervals) { this.attendanceIntervals = attendanceIntervals; }
}

Loading

0 comments on commit 9326b20

Please sign in to comment.