├── .gitignore ├── LICENCE ├── README.md ├── lowdefy.yaml └── pages └── report ├── components ├── genre_counts_bar_chart.yaml ├── genre_counts_pie_chart.yaml └── ratings_table.yaml ├── report.yaml └── requests ├── get_scores_by_genre.yaml └── get_top_100_score_difference_movies.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .lowdefy/** 2 | .env -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lowdefy Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📊 Lowdefy Reporting Example 2 | 3 | > [View this example.](https://example-reporting.lowdefy.com) 4 | 5 | This example demonstrates useful patterns for building a BI report/dashboard pages in Lowdefy. It connects to a MongoDB database with the Atlas Movies sample dataset pre-loaded. 6 | 7 | ## ⚙️ Running this example 8 | 9 | - Create a MongoDB cluster and get a URI connection string: 10 | - Create a free MongoDB database cluster hosted by [MongoDB Atlas](https://www.mongodb.com/try). 11 | - Load the Atlas sample dataset. 12 | - In the Database access section, create a database user with read access to any database (You can also specify the database as `sample_mflix`). 13 | - In the main cluster view, click "connect", then "Connect your application". This will give a MongoDB URI connection string. Use the credentials you just created. 14 | - You can read more about the [Lowdefy MongoDB connector](https://docs.lowdefy.com/MongoDB). 15 | - Clone this repository. 16 | - Create a `.env` file in your project folder and set your MongoDB database connector URI as a variable in the `.env` file: `LOWDEFY_SECRET_EXAMPLES_MDB="{{ your_mongodb_connection_uri }}"` 17 | - In the command console, navigate to your project folder and run the Lowdefy CLI: `pnpx lowdefy@4 dev`. 18 | 19 | ## 🔗 More Lowdefy resources 20 | 21 | - Getting started with Lowdefy - https://docs.lowdefy.com/tutorial-start 22 | - Lowdefy docs - https://docs.lowdefy.com 23 | - Lowdefy website - https://lowdefy.com 24 | - Community forum - https://github.com/lowdefy/lowdefy/discussions 25 | - Bug reports and feature requests - https://github.com/lowdefy/lowdefy/issues 26 | - Discord - https://discord.gg/WmcJgXt 27 | 28 | ## ⚖️ Licence 29 | 30 | [MIT](https://github.com/lowdefy/lowdefy-example-reporting/blob/main/LICENSE) 31 | -------------------------------------------------------------------------------- /lowdefy.yaml: -------------------------------------------------------------------------------- 1 | name: Lowdefy Reporting Example 2 | lowdefy: 4.0.1 3 | license: MIT 4 | 5 | # description 6 | # This example show patterns that can be used to implement a BI report/dashboard. 7 | # It assumes that it is connected to a MongoDB database with the Atlas sample dataset loaded. 8 | 9 | # Define all the data connections, in this case the brands and products MongoDB collections 10 | connections: 11 | - id: movies_mongodb # The connectionId that will be used when defining requests and mutations on our pages 12 | type: MongoDBCollection 13 | properties: 14 | databaseName: sample_mflix # The database name 15 | collection: movies # The collection name 16 | databaseUri: 17 | _secret: EXAMPLES_MDB # The database connection uri that is stored as a secret and accessed using the _secret operator 18 | 19 | # Menus used in the app can be listed here 20 | # By default, the menu with id default, or the first menu defined is used. 21 | # If no menu is defined, a default menu is created using all the defined pages. 22 | menus: 23 | - id: default 24 | links: 25 | - id: report # Define the menu link that directs to the report page 26 | type: MenuLink 27 | pageId: report # Id of the report page 28 | properties: 29 | title: Report # Title to show on the menu 30 | icon: AiOutlineLineChart 31 | 32 | # All the pages in the app are listed here 33 | # Instead of defining the page in the lowdefy.yaml file, it is defined in its own yaml file and referenced here 34 | pages: 35 | - _ref: pages/report/report.yaml 36 | -------------------------------------------------------------------------------- /pages/report/components/genre_counts_bar_chart.yaml: -------------------------------------------------------------------------------- 1 | id: genre_counts_bar_chart 2 | type: EChart 3 | properties: 4 | height: 400 5 | option: 6 | dataset: 7 | source: 8 | _request: get_scores_by_genre # Use get_scores_by_genre request for chart data 9 | legend: 10 | show: true 11 | bottom: 0 # Display legend below chart 12 | grid: 13 | bottom: 100 14 | tooltip: 15 | show: true 16 | trigger: item 17 | xAxis: 18 | type: category # Add a category for the x axis 19 | data: 20 | _array.map: # Map over the data and get the list of _ids which will serve as our categories 21 | - _request: get_scores_by_genre 22 | - _function: 23 | __args: 0._id 24 | axisLabel: 25 | rotate: 60 # Rotate the labels 26 | yAxis: 27 | - type: value # Add a value for the y axis 28 | name: Rating # Give the y axis a title 29 | nameRotate: 90 ß 30 | nameLocation: middle 31 | nameGap: 40 32 | min: # Set minimum y value 33 | _function: 34 | __math.floor: 35 | __if_none: 36 | - __args: 0.min 37 | - 0 38 | series: 39 | - type: bar # Create a column series to show columns 40 | name: Critics Rating 41 | itemStyle: 42 | color: "#5D7092" # Set column fill color 43 | borderColor: "#5D7092" # Set column border color 44 | encode: 45 | x: _id # Set the category value to the field _id in the data 46 | y: criticRating # Set the value value to the field in the data 47 | - type: bar 48 | name: Viewer Rating 49 | itemStyle: 50 | color: "#5AD8A6" 51 | borderColor: "#5AD8A6" 52 | encode: 53 | x: _id 54 | y: viewerRating 55 | -------------------------------------------------------------------------------- /pages/report/components/genre_counts_pie_chart.yaml: -------------------------------------------------------------------------------- 1 | id: genre_counts_pie_chart 2 | type: EChart 3 | properties: 4 | height: 400 5 | option: 6 | series: 7 | - name: genre_counts 8 | type: pie 9 | radius: [30%, 50%] # Make the chart a donut chart 10 | label: 11 | fontSize: 12 12 | data: 13 | _mql.aggregate: # Format data to have fields name and value 14 | on: 15 | _request: get_scores_by_genre # Share the same request as the bar chart 16 | pipeline: 17 | - $project: 18 | name: $_id 19 | value: $count 20 | - $sort: 21 | value: -1 22 | color: # Add custom colors 23 | - "#122C6A" 24 | - "#0044A4" 25 | - "#005BBF" 26 | - "#3874DB" 27 | - "#5A8DF8" 28 | - "#7EABFF" 29 | -------------------------------------------------------------------------------- /pages/report/components/ratings_table.yaml: -------------------------------------------------------------------------------- 1 | id: ratings_table 2 | type: AgGridAlpine 3 | properties: 4 | theme: basic 5 | rowData: 6 | _request: get_top_100_score_difference_movies 7 | defaultColDef: # Define default column definitions that apply to all the defined columns 8 | sortable: true # Enables sorting on the columns when the header is clicked 9 | resizable: true # Enables resizing of column widths 10 | filter: true # Enables filtering of the columns using agGrid's default filter 11 | columnDefs: # Define all the columns 12 | - headerName: Title # Display name 13 | field: title # The field name in the data 14 | minWidth: 350 15 | flex: 1 0 auto 16 | - headerName: Year 17 | field: year 18 | width: 100 19 | - headerName: Difference 20 | field: difference 21 | width: 160 22 | type: numericColumn # Setting this aligns the number on the right 23 | valueFormatter: 24 | _function: # Provide a fprmatter function to pretty render the data value. 25 | __intl.numberFormat: 26 | on: 27 | __args: 0.value 28 | params: 29 | options: 30 | minimumFractionDigits: 1 # Format the number with 1 decimal place 31 | - headerName: Viewer Rating 32 | field: viewerRating 33 | width: 160 34 | type: numericColumn 35 | valueFormatter: 36 | _function: 37 | __intl.numberFormat: 38 | on: 39 | __args: 0.value 40 | params: 41 | options: 42 | maximumFractionDigits: 1 43 | - headerName: Critic Rating 44 | field: criticRating 45 | width: 160 46 | type: numericColumn 47 | valueFormatter: 48 | _function: 49 | __intl.numberFormat: 50 | on: 51 | __args: 0.value 52 | params: 53 | options: 54 | maximumFractionDigits: 1 55 | - headerName: Viewer Reviews 56 | field: viewerReviews 57 | width: 160 58 | type: numericColumn 59 | valueFormatter: 60 | _function: 61 | __intl.numberFormat: 62 | on: 63 | __args: 0.value 64 | params: 65 | options: 66 | maximumFractionDigits: 0 67 | - headerName: Critic Reviews 68 | field: criticReviews 69 | width: 160 70 | type: numericColumn 71 | valueFormatter: 72 | _function: 73 | __intl.numberFormat: 74 | on: 75 | __args: 0.value 76 | params: 77 | options: 78 | maximumFractionDigits: 0 79 | -------------------------------------------------------------------------------- /pages/report/report.yaml: -------------------------------------------------------------------------------- 1 | id: report 2 | type: PageHeaderMenu 3 | properties: 4 | title: Report 5 | layout: 6 | contentGutter: 16 # Set a gutter of 16px between all the cards on the page 7 | requests: 8 | # Requests are referenced from their own yaml files in the requests folder 9 | - _ref: pages/report/requests/get_scores_by_genre.yaml 10 | - _ref: pages/report/requests/get_top_100_score_difference_movies.yaml 11 | 12 | events: 13 | # A list of actions that gets completed when this page is first loaded. 14 | onInitAsync: 15 | - id: fetch_data # Fetch the request data before the page renders in order to populate the charts 16 | type: Request 17 | params: 18 | - get_scores_by_genre 19 | - get_top_100_score_difference_movies 20 | 21 | areas: 22 | content: 23 | blocks: 24 | - id: title # Title on page 25 | type: Title 26 | properties: 27 | content: Movie Critic and Viewer Ratings 28 | level: 4 29 | - id: genre_counts_bar_chart_card 30 | type: Card 31 | properties: 32 | title: Comparison of Critic and Viewer Ratings by Genre 33 | layout: 34 | span: 16 # Make the card span 2 thirds of the screen 35 | blocks: 36 | # The bar chart is defined in its own yaml file and referenced here 37 | - _ref: pages/report/components/genre_counts_bar_chart.yaml 38 | 39 | - id: pie_chart_card 40 | type: Card 41 | layout: 42 | span: 8 43 | properties: 44 | title: Genre Counts 45 | blocks: 46 | # The pie chart is defined in its own yaml file and referenced here 47 | - _ref: pages/report/components/genre_counts_pie_chart.yaml 48 | 49 | - id: table_card 50 | type: Card 51 | properties: 52 | title: 100 Movies with Largest Difference between Critic and Viewer Ratings 53 | blocks: 54 | # The table is defined in its own yaml file and referenced here 55 | - _ref: pages/report/components/ratings_table.yaml 56 | header: 57 | blocks: 58 | - id: affix 59 | type: Affix 60 | blocks: 61 | - id: source_button 62 | type: Button 63 | properties: 64 | icon: AiOutlineGithub 65 | title: View App Source Code 66 | type: default 67 | shape: round 68 | events: 69 | onClick: 70 | - id: link_repo 71 | type: Link 72 | params: 73 | url: https://github.com/lowdefy/lowdefy-example-reporting 74 | newTab: true 75 | -------------------------------------------------------------------------------- /pages/report/requests/get_scores_by_genre.yaml: -------------------------------------------------------------------------------- 1 | # Request for the bar and pie charts 2 | id: get_scores_by_genre 3 | type: MongoDBAggregation # MongoDB Aggregation to get the data 4 | connectionId: movies_mongodb 5 | properties: 6 | pipeline: 7 | - $unwind: 8 | path: $genres # Genres is an array, so unwind to create 1 document for every array entry 9 | - $match: # Only look at top 6 genres 10 | genres: 11 | $in: 12 | - Drama 13 | - Comedy 14 | - Romance 15 | - Crime 16 | - Thriller 17 | - Action 18 | - $group: 19 | # Calculate the average Rotten Tomatoes viewer and critic ratings for each genre. 20 | _id: $genres # Group data by the genre field 21 | viewerRating: 22 | $avg: $tomatoes.viewer.rating 23 | criticRating: 24 | $avg: $tomatoes.critic.rating 25 | count: 26 | $sum: 1 # Count the number of documents by summing 1 for every document 27 | - $addFields: 28 | # Multiply viewerRating by 2 as it is out of 5 not 10. 29 | viewerRating: 30 | $multiply: 31 | - $viewerRating 32 | - 2 33 | - $sort: 34 | count: -1 # Sort by descending count 35 | -------------------------------------------------------------------------------- /pages/report/requests/get_top_100_score_difference_movies.yaml: -------------------------------------------------------------------------------- 1 | # Request for the table 2 | id: get_top_100_score_difference_movies 3 | type: MongoDBAggregation # MongoDB Aggregation to get the data 4 | connectionId: movies_mongodb 5 | properties: 6 | pipeline: 7 | - $match: 8 | tomatoes.critic.numReviews: # Match where there are 20 or more critic reviews 9 | $gte: 20 10 | tomatoes.viewer.numReviews: # and 100 or more viewer reviews 11 | $gte: 100 12 | genres: # Only look at top 6 genres 13 | $in: 14 | - Drama 15 | - Comedy 16 | - Romance 17 | - Crime 18 | - Thriller 19 | - Action 20 | - $project: # Include fields we want to show in the table 21 | title: 1 22 | year: 1 23 | rated: 1 24 | viewerRating: # Multiply viewerRating by 2 as it is out of 5 not 10. 25 | $multiply: 26 | - $tomatoes.viewer.rating 27 | - 2 28 | criticRating: $tomatoes.critic.rating 29 | viewerReviews: $tomatoes.viewer.numReviews 30 | criticReviews: $tomatoes.critic.numReviews 31 | difference: # Calculate the difference between the critic and viewer scores 32 | $abs: # Take the absolute (positive) value 33 | $subtract: 34 | - $multiply: 35 | - $tomatoes.viewer.rating 36 | - 2 37 | - $tomatoes.critic.rating 38 | - $sort: 39 | difference: -1 # Sort by biggest difference 40 | - $limit: 100 # Only return the first 100 results 41 | --------------------------------------------------------------------------------