-
Notifications
You must be signed in to change notification settings - Fork 4
SED-4417 grid-layout-for-executions-report #603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
41023f3
e643bba
12b4113
c68fcdb
bf259ca
34bb57c
75bf5bc
1828216
125297c
a6acf75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| { | ||
| "id": "69b010aeec94534eb48176db", | ||
| "name": "Step Main Layout", | ||
| "layout": { | ||
| "widgets": [ | ||
| { | ||
| "widgetType": "errorsWidget", | ||
| "position": { | ||
| "row": 1, | ||
| "column": 1, | ||
| "widthInCells": 8, | ||
| "heightInCells": 1 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "testCases", | ||
| "position": { | ||
| "row": 2, | ||
| "column": 1, | ||
| "widthInCells": 6, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "testCasesSummary", | ||
| "position": { | ||
| "row": 2, | ||
| "column": 7, | ||
| "widthInCells": 2, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "keywordsSummary", | ||
| "position": { | ||
| "row": 5, | ||
| "column": 1, | ||
| "widthInCells": 2, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "executionTree", | ||
| "position": { | ||
| "row": 5, | ||
| "column": 3, | ||
| "widthInCells": 6, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "keywordsList", | ||
| "position": { | ||
| "row": 8, | ||
| "column": 1, | ||
| "widthInCells": 6, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "performanceOverview", | ||
| "position": { | ||
| "row": 8, | ||
| "column": 7, | ||
| "widthInCells": 2, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "errors", | ||
| "position": { | ||
| "row": 11, | ||
| "column": 1, | ||
| "widthInCells": 8, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "notificationSubscriptionForExecution", | ||
| "position": { | ||
| "row": 14, | ||
| "column": 1, | ||
| "widthInCells": 4, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "housekeepingSettingsForExecution", | ||
| "position": { | ||
| "row": 14, | ||
| "column": 5, | ||
| "widthInCells": 4, | ||
| "heightInCells": 3 | ||
| } | ||
| }, | ||
| { | ||
| "widgetType": "currentOperations", | ||
| "position": { | ||
| "row": 17, | ||
| "column": 1, | ||
| "widthInCells": 4, | ||
| "heightInCells": 3 | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package step.core.reporting; | ||
|
|
||
| import step.core.accessors.AbstractAccessor; | ||
| import step.core.accessors.AbstractOrganizableObject; | ||
| import step.core.collections.Collection; | ||
| import step.core.collections.Filters; | ||
| import step.core.collections.SearchOrder; | ||
| import step.core.collections.filters.Or; | ||
| import step.core.reporting.model.ReportLayout; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import static step.core.collections.Order.ASC; | ||
| import static step.core.reporting.model.ReportLayout.FIELD_VISIBILITY; | ||
| import static step.core.reporting.model.ReportLayout.ReportLayoutVisibility.Preset; | ||
| import static step.core.reporting.model.ReportLayout.ReportLayoutVisibility.Shared; | ||
|
|
||
| public class ReportLayoutAccessor extends AbstractAccessor<ReportLayout> { | ||
|
|
||
| public ReportLayoutAccessor(Collection<ReportLayout> collectionDriver) { | ||
| super(collectionDriver); | ||
| } | ||
|
|
||
| public List<ReportLayout> getAccessibleReportLayoutsDefinitions(String userName) { | ||
| Or ownerOrShared = Filters.or(List.of(Filters.equals(FIELD_VISIBILITY, Preset.name()), Filters.equals(FIELD_VISIBILITY, Shared.name()), Filters.equals("creationUser", userName))); | ||
| return this.getCollectionDriver() | ||
| .find(ownerOrShared, new SearchOrder(ATTRIBUTES_FIELD_NAME + "." + AbstractOrganizableObject.NAME, ASC.numeric), null, null, 0) | ||
| .peek(reportLayout -> reportLayout.layout = null) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| package step.core.reporting; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import org.bson.types.ObjectId; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import step.core.GlobalContext; | ||
| import step.core.accessors.AbstractOrganizableObject; | ||
| import step.core.accessors.DefaultJacksonMapperProvider; | ||
| import step.core.collections.Collection; | ||
| import step.core.collections.Filters; | ||
| import step.core.deployment.WebApplicationConfigurationManager; | ||
| import step.core.entities.Entity; | ||
| import step.core.entities.EntityConstants; | ||
| import step.core.plugins.AbstractControllerPlugin; | ||
| import step.core.plugins.Plugin; | ||
| import step.core.reporting.model.ReportLayout; | ||
| import step.core.reporting.model.ReportLayoutJson; | ||
| import step.framework.server.tables.Table; | ||
| import step.framework.server.tables.TableRegistry; | ||
|
|
||
| import java.io.File; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| @Plugin | ||
| public class ReportLayoutPlugin extends AbstractControllerPlugin { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(ReportLayoutPlugin.class); | ||
|
|
||
| public static final String DEFAULT_REPORT_LAYOUT = "Default"; | ||
| public static final String PRESET_FOLDER_PATH_CONFIG_KEY = "plugins.reporting.layouts.presets.folder"; | ||
| public static final String PRESET_FOLDER_PATH_DEFAULT = "../plugins/reporting/layouts"; | ||
| public static final String DEFAULT_LAYOUT_ID_CONFIG_KEY = "plugins.reporting.layouts.default.id"; | ||
| public static final String DEFAULT_LAYOUT_ID_DEFAULT = "69b010aeec94534eb48176db"; | ||
|
|
||
| private ReportLayoutAccessor reportLayoutAccessor; | ||
| private String defaultLayoutId; | ||
|
|
||
| @Override | ||
| public void serverStart(GlobalContext context) throws Exception { | ||
| super.serverStart(context); | ||
| TableRegistry tableRegistry = context.require(TableRegistry.class); | ||
| //Create accessor | ||
| Collection<ReportLayout> reportLayoutCollection = context.getCollectionFactory().getCollection(EntityConstants.reportLayouts, ReportLayout.class); | ||
| reportLayoutAccessor = new ReportLayoutAccessor(reportLayoutCollection); | ||
| context.put(ReportLayoutAccessor.class, reportLayoutAccessor); | ||
| //Register entity | ||
| context.getEntityManager().register(new Entity<>(EntityConstants.reportLayouts, reportLayoutAccessor, ReportLayout.class)); | ||
| //Register Table, table only return layout metadata not the layout itself | ||
| tableRegistry.register(EntityConstants.reportLayouts, new Table<>(reportLayoutCollection, ReportLayoutServices.REPORT_LAYOUT_RIGHT + "-read", false).withResultItemTransformer((reportLayout, session) -> { | ||
| reportLayout.layout = null; | ||
| return reportLayout; | ||
| })); | ||
|
Comment on lines
+52
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The table for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The table is created with restriction for the report layout read right, only authenticated user with this specific right can access the list of layout (stripped from the actual layout content). I don't see it as an issue in this case. |
||
| //Register Services | ||
| context.getServiceRegistrationCallback().registerService(ReportLayoutServices.class); | ||
|
|
||
| //Add default layout ID to UI configuration | ||
| defaultLayoutId = context.getConfiguration().getProperty(DEFAULT_LAYOUT_ID_CONFIG_KEY, DEFAULT_LAYOUT_ID_DEFAULT); | ||
| WebApplicationConfigurationManager configurationManager = context.require(WebApplicationConfigurationManager.class); | ||
| configurationManager.registerHook(s -> Map.of(DEFAULT_LAYOUT_ID_CONFIG_KEY, defaultLayoutId)); | ||
|
|
||
| } | ||
|
|
||
| @Override | ||
| public void initializeData(GlobalContext context) throws Exception { | ||
| super.initializeData(context); | ||
| // Drop all existing presets - the folder is the source of truth at startup | ||
| reportLayoutAccessor.getCollectionDriver().remove( | ||
| Filters.equals(ReportLayout.FIELD_VISIBILITY, ReportLayout.ReportLayoutVisibility.Preset.name())); | ||
|
|
||
| // Load presets from the configured folder | ||
| File presetsFolder = new File(context.getConfiguration().getProperty(PRESET_FOLDER_PATH_CONFIG_KEY, PRESET_FOLDER_PATH_DEFAULT)); | ||
| if (presetsFolder.exists() && presetsFolder.isDirectory()) { | ||
| ObjectMapper objectMapper = DefaultJacksonMapperProvider.getObjectMapper(); | ||
| File[] jsonFiles = presetsFolder.listFiles((dir, name) -> name.endsWith(".json")); | ||
| if (jsonFiles != null) { | ||
| for (File jsonFile : jsonFiles) { | ||
| try { | ||
| ReportLayoutJson layoutJson = objectMapper.readValue(jsonFile, ReportLayoutJson.class); | ||
| if (ObjectId.isValid(layoutJson.id)) { | ||
| ReportLayout reportLayout = new ReportLayout(layoutJson.layout, ReportLayout.ReportLayoutVisibility.Preset); | ||
| reportLayout.addAttribute(AbstractOrganizableObject.NAME, layoutJson.name); | ||
| reportLayout.setId(new ObjectId(layoutJson.id)); | ||
| reportLayoutAccessor.save(reportLayout); | ||
| } else { | ||
| logger.error("Invalid json file: {}, the id {} has been tempered with and is not a valid ObjectId", jsonFile.getAbsolutePath(), layoutJson.id); | ||
| } | ||
| } catch (Exception e) { | ||
| logger.error("Failed to load preset layout from file '{}'", jsonFile.getAbsolutePath(), e); | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| logger.warn("The configured presets folder '{}' does not exist or is not a directory.", presetsFolder.getAbsolutePath()); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simply to be coherent with other entities and be ready if a managed layouts view is required in the future. Do you see any issue keeping it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really. If we'll anyway need it in the future it doesn't hurt. Concerning the gemini comment below, it is not a security issue as we remove the content