====== Reports module - Creating a report ====== {{tag> report}} [[devel:documentation:modules_rpt | Reports]] module was added in CzechIdM 7.6.0. The aim of this tutorial is to show the way, how to **create** your own **report**. We will be creating a new report with all identities in ''xlsx'' format and we want to have report with enabled / disabled identities, so we want to search identities by their enabled status. This filter parameter will be optional - all identities will be in report, if activity is not selected. Source codes for this tutorial can be found in the [[https://github.com/bcvsolutions/CzechIdMng/tree/develop/Realization/backend/example/src/main/java/eu/bcvsolutions/idm/example/report|example module]] ===== What do you need before you start ===== * You need to install CzechIdM 7.6.0 (and higher). We have CzechIdM installed for this tutorial on server ''http://localhost:8080/idm''. * Create identity, which will have permission to create and read reports. We are using demo ''admin:admin'' identity. ===== 01 Create report ===== We need to implement ''ReportExecutor'' (~report), which generates output report data in json format. This data are saved as attachment. Report loads identities secured be read permission - identity, which will use this report has to have permission to read identities. This can be used e.g. for managers - they will see only identities, which can read => their subordinates. Identity without permission to read identities will see empty report only (if this identity will have permission to create reports). Loaded identities are streamed into temp file and after the end attachment is created and returned. Methods from ''AbstractReportExecutor'' super class are used. @Component("exampleIdentityReportExecutor") @Description("Identities - example") public class IdentityReportExecutor extends AbstractReportExecutor { public static final String REPORT_NAME = "example-identity-report"; // report ~ executor name // @Autowired private IdmIdentityService identityService; /** * Report ~ executor name */ @Override public String getName() { return REPORT_NAME; } /** * Filter form attributes: * - enabled / disabled identities */ @Override public List getFormAttributes() { IdmFormAttributeDto disabled = new IdmFormAttributeDto( IdmIdentityFilter.PARAMETER_DISABLED, "Disabled identities", PersistentType.BOOLEAN); // we want select box instead simple checkbox (null value is needed) disabled.setFaceType(BaseFaceType.BOOLEAN_SELECT); disabled.setPlaceholder("All identities or select ..."); return Lists.newArrayList(disabled); } @Override protected IdmAttachmentDto generateData(RptReportDto report) { // prepare temp file for json stream File temp = getAttachmentManager().createTempFile(); // try (FileOutputStream outputStream = new FileOutputStream(temp)) { // write into json stream JsonGenerator jGenerator = getMapper().getFactory().createGenerator(outputStream, JsonEncoding.UTF8); try { // json will be array of identities jGenerator.writeStartArray(); // form instance has useful methods to transform form values IdmFormInstanceDto formInstance = new IdmFormInstanceDto(report, getFormDefinition(), report.getFilter()); // initialize filter by given form - transform to multi value map // => form attribute defined above will be automaticaly mapped to identity filter IdmIdentityFilter filter = new IdmIdentityFilter(formInstance.toMultiValueMap()); // report extends long running task - show progress by count and counter lrt attributes counter = 0L; // find a first page of identities Pageable pageable = PageRequest.of(0, 100, new Sort(Direction.ASC, IdmIdentity_.username.getName())); do { Page identities = identityService.find(filter, pageable, IdmBasePermission.READ); if (count == null) { // report extends long running task - show progress by count and counter lrt attributes count = identities.getTotalElements(); } boolean canContinue = true; for (Iterator i = identities.iterator(); i.hasNext() && canContinue;) { // write single identity into json getMapper().writeValue(jGenerator, i.next()); // // supports cancel report generating (report extends long running task) ++counter; canContinue = updateState(); } // iterate while next page of identities is available pageable = identities.hasNext() && canContinue ? identities.nextPageable() : null; } while (pageable != null); // // close array of identities jGenerator.writeEndArray(); } finally { // close json stream jGenerator.close(); } // save create temp file with array of identities in json as attachment return createAttachment(report, new FileInputStream(temp)); } catch (IOException ex) { throw new ReportGenerateException(report.getName(), ex); } finally { FileUtils.deleteQuietly(temp); } } } Generated output json data in attachment will be used in renderer. ===== 02 Create renderer ===== Renderer loads generated data prepared in previous step and transform them into ''xlsx'' format. This output will be provided to download by report module rest controller. @Component("exampleIdentityReportRenderer") @Description(AbstractXlsxRenderer.RENDERER_EXTENSION) // will be show as format for download public class IdentityReportRenderer extends AbstractXlsxRenderer { @Override public InputStream render(RptReportDto report) { try { // read json stream JsonParser jParser = getMapper().getFactory().createParser(getReportData(report)); XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("Report"); // header Row row = sheet.createRow(0); Cell cell = row.createCell(0); cell.setCellValue("Id"); cell = row.createCell(1); cell.setCellValue("Username"); cell = row.createCell(2); cell.setCellValue("First name"); cell = row.createCell(3); cell.setCellValue("Last name"); cell = row.createCell(4); cell.setCellValue("Disabled"); int rowNum = 1; // // json is array of identities if (jParser.nextToken() == JsonToken.START_ARRAY) { // write single identity while (jParser.nextToken() == JsonToken.START_OBJECT) { IdmIdentityDto identity = getMapper().readValue(jParser, IdmIdentityDto.class); row = sheet.createRow(rowNum++); cell = row.createCell(0); cell.setCellValue(identity.getId().toString()); cell = row.createCell(1); cell.setCellValue(identity.getUsername()); cell = row.createCell(2); cell.setCellValue(identity.getFirstName()); cell = row.createCell(3); cell.setCellValue(identity.getLastName()); cell = row.createCell(4); cell.setCellValue(identity.isDisabled()); } } // close json stream jParser.close(); // // close and return input stream return getInputStream(workbook); } catch (IOException ex) { throw new ReportRenderException(report.getName(), ex); } } } ===== 03 Register renderer ===== We have report and renderer, but we need to register renterer with report => renderer can render this report. We will use ''RendererRegistrar'' to register renderer to report - we only implement this interface directly in renderer - see ''register'' method. @Component("exampleIdentityReportRenderer") @Description(AbstractXlsxRenderer.RENDERER_EXTENSION) // will be show as format for download public class IdentityReportRenderer extends AbstractXlsxRenderer implements RendererRegistrar { ... /** * Register renderer to example report */ @Override public String[] register(String reportName) { if (IdentityReportExecutor.REPORT_NAME.equals(reportName)) { return new String[]{ getName() }; } return new String[]{}; } } The other way is to create standalone registrar class e.g. by extending ''AbstractRendererRegistrar''. ===== 04 Test on frontend ===== Log in to application, go to the report agenda and click on add button on the right: {{:tutorial:dev:example-report.png|}} Congratulations, your own report is created. ===== 05 Integration test ===== Create integration test for report. public class IdentityReportExecutorIntegrationTest extends AbstractIntegrationTest { @Autowired private TestHelper helper; @Autowired private IdentityReportExecutor reportExecutor; @Autowired private IdmIdentityService identityService; @Autowired private AttachmentManager attachmentManager; @Qualifier("objectMapper") @Autowired private ObjectMapper mapper; @Autowired private LoginService loginService; @Autowired private IdentityReportRenderer xlsxRenderer; @Before public void before() { // report checks authorization policies - we need to log in loginService.login(new LoginDto(InitTestData.TEST_ADMIN_USERNAME, new GuardedString(InitTestData.TEST_ADMIN_PASSWORD))); } @After public void after() { super.logout(); } @Test @Transactional public void testDisabledIdentity() throws IOException { // prepare test identities IdmIdentityDto identityOne = helper.createIdentity(); IdmIdentityDto identityDisabled = helper.createIdentity(); identityService.disable(identityDisabled.getId()); // // prepare report filter RptReportDto report = new RptReportDto(UUID.randomUUID()); report.setExecutorName(reportExecutor.getName()); IdmFormDto filter = new IdmFormDto(); IdmFormDefinitionDto definition = reportExecutor.getFormDefinition(); IdmFormValueDto disabled = new IdmFormValueDto(definition.getMappedAttributeByCode(IdmIdentityFilter.PARAMETER_DISABLED)); disabled.setValue(false); filter.getValues().add(disabled); filter.setFormDefinition(definition.getId()); report.setFilter(filter); // // generate report report = reportExecutor.generate(report); Assert.assertNotNull(report.getData()); List identityRoles = mapper.readValue( attachmentManager.getAttachmentData(report.getData()), new TypeReference>(){}); // // test Assert.assertTrue(identityRoles.stream().anyMatch(i -> i.equals(identityOne))); Assert.assertFalse(identityRoles.stream().anyMatch(i -> i.equals(identityDisabled))); // attachmentManager.deleteAttachments(report); } @Test @Transactional public void testRenderers() { helper.createIdentity(); // // prepare report filter RptReportDto report = new RptReportDto(UUID.randomUUID()); report.setExecutorName(reportExecutor.getName()); // // generate report report = reportExecutor.generate(report); // Assert.assertNotNull(xlsxRenderer.render(report)); } } ===== Advanced ===== ==== 06 Add @Enabled annotation ==== When your report and his renderer is created in custom module, which can be disabled, then you need to add ''@Enabled(YourModuleId)'' annotation on report and renderer classes. @Enabled(ExampleModuleDescriptor.MODULE_ID) @Component("exampleIdentityReportExecutor") @Description("Identities - example") public class IdentityReportExecutor extends AbstractReportExecutor { ... } @Enabled(ExampleModuleDescriptor.MODULE_ID) @Component("exampleIdentityReportRenderer") @Description(AbstractXlsxRenderer.RENDERER_EXTENSION) // will be show as format for download public class IdentityReportRenderer extends AbstractXlsxRenderer implements RendererRegistrar { ... } ==== 07 Send notification ==== @since 10.6.0 Notification with rendered report can be sent. All reports have configuration property ''Notification'' - send notification to given topic (by notification configuration) after report is successfuly created. Notification is sent to report creator (identity) by default. If report is scheduled, recipient has to be configured in notification configuration (report creator is system). All report renderes (except default json renderer) are used and generated reports are attached to notification => e.g. email is sent with ''xlsx'' attachment included.