OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.42k stars 2.39k forks source link

Current widgets / Types do Not support Metrics / Graphs #10995

Closed fasteddys closed 2 years ago

fasteddys commented 2 years ago

Which component Strategy for new developers & Students to build metrics based components

There's some confusion in the Developers & students community. they do not know, which path to take to build transactional components/functionality, for e.g. if a developer wants to create a component with amaster-details table for customer shipping/orders tables Or, GRAPH for e.g. Number of returns per product D3 / eCharts Graph, and a table below with details on selected row-item in the graph... which one / do they use a module, do they use a widget, or content type its very fuzzy on which one to pick??

Next, if a developer wants to scaffold Orchard Form, or custom Widgets , module, for e.g. in the last .net dev meetup many mentioned, they don't know how to create a simple pie chart in orchard for e.g. there are several use cases like a customer returns per product.

I posted a help request in the Lombiq CVS2022 extension, and proposed using Extension, they gave up after trying, first because of the compatibility issue, which is being fixed, but also some functionality issue. So, if it has more comments/picture on what the code structure/components its scaffolding, step 1, 2 etc. in comments form then developers would be able to use it to easily create Module or Widget.

They key thing to understand is the flexibility in your scaffolding / visual extension is perhaps a good entry point as a visual tool in VS to help them create the components they want.

If you notice in the admin dashboard there is no single graph there?

Can you create a core component, with a graph and table to show number of failed attempts, IP aaddress (kinda like WordFence or loginizer in WP), this would be a good reference app.

sebastienros commented 2 years ago

for e.g. in the last .net dev meetup many mentioned

Source?

Can you create a core component, with a graph and table to show number of failed attempts...

Yes you can

Skrypt commented 2 years ago

It's been triaged for documentation. Keep it open.

I believe @sebastienros works with graphs all day long for its daytime job for analyzing ASP.NET Core performance. I can't remember which library exactly he's using but I believe that everything is possible in Orchard Core. If you want to create widgets in the Dashboard then it's a matter of creating one that will use one of these javascript graphs libraries to display data that comes from the database. As for a single tutorial to do this, I think there are many ways to develop things in Orchard Core. Here, you seem to want something that would be manageable from the admin UI which would use prepared datasets (Queries module). So, you would need some guidance on steps to build something like this which is something we generally don't do here unless this is a feature that might be added in the source code for everyone. I would see this more as an external contrib module because it is really specific.

Here, if we would need to report statistics for an ECommerce module we would probably implement this in the OrchardCore.Commerce module as a OrchardCore.Commerce contrib module that adds widgets for the admin UI dashboard.

I believe that Widgets shapes can be added from the Admin UI by using the "Templates" feature. Though, when it comes to consume a javascript library I generally build an entire module that builds these Widgets. Else, it is also totally doable from the admin UI if you don't need to create handlers and drivers for your custom shapes.

Here, a graph generally needs some specific data and caching over that data because else it can be quite demanding when building complex SQL Queries. So, first, this is about understanding how YesSQL works for building these SQL queries from a managed context. Then, you need to know how to build also database tables with relationships through YesSQL if you want to have one to many, many to many relationships ... Here, I can't say more because I would need to start looking at a simple example of what you need to achieve and from which javascript library. Else, as for the data itself, I think it is about learning to build things with YesSQL / Dapper.

So here, I'm assuming when you say "master -> detail table" you mean one to many relationship in a database. And, the confusion is that most of our documentation is about how to use the map/reduce design pattern with YesSQL.

Skrypt commented 2 years ago

Here is a SQL diagram that shows a many-to-many relationship.

tables-relations3

If we start from a Migrations.cs file in a module to create these tables we can do something like this :

using System;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.ContentManagement.Metadata.Settings;
using OrchardCore.Data.Migration;
using YesSql.Sql;

namespace YourNamespace
{
    public class Migrations : DataMigration
    {
        public int Create()
        {
            // We create the 3 tables first
            SchemaBuilder.CreateTable(nameof(Employee), table => table
                .Column<int>(nameof(Employee.EmployeeID), col => col.PrimaryKey().Identity())
                .Column<string>(nameof(IndexingTask.FirstName), c => c.WithLength(50))
                .Column<string>(nameof(IndexingTask.LastName), col => c.WithLength(50))
            );

            // This is the "middle" table that stores the many to many references
            SchemaBuilder.CreateTable(nameof(EmployeeSkill), table => table
                .Column<int>(nameof(Employee.EmployeeID))
                .Column<int>(nameof(SkillDescription.SkillID))
            );

            SchemaBuilder.CreateTable(nameof(SkillDescription), table => table
                .Column<int>(nameof(SkillDescription.SkillID), col => col.PrimaryKey().Identity())
                .Column<string>(nameof(SkillDescription.Description), c => c.WithLength(30))
            );

            // We create the foreign keys that will create the many to many relationship
            SchemaBuilder.CreateForeignKey(
                     "fk_" + nameof(Employee) + "_" + nameof(SkillDescription), // foreign key name
                     nameof(Employee),  // source table name
                     new string [] { "EmployeeId" },  // source table columns to include in the FK
                     nameof(EmployeeSkill),  // destination table name
                     new string [] { "EmployeeId" }  // destination table name columns to include in the FK
            );

            SchemaBuilder.CreateForeignKey(
                     "fk_" + nameof(EmployeeSkill) + "_" + nameof(SkillDescription), // foreign key name
                     nameof(EmployeeSkill),  // source table name
                     new string [] { "SkillID" },  // source table columns to include in the FK
                     nameof(SkillDescription),  // destination table name
                     new string [] { "SkillID" }  // destination table name columns to include in the FK
            );

            return 1;
        }
    }
}

Consider this as a draft as I didn't try this code yet.

Question is now : how do we use this in the context of YesSQL. We can't use these type of queries. I need to read.

_session.Query<ContentItem, ContentItemIndex();
Skrypt commented 2 years ago

I'm thinking that I'm maybe making a mistake there where I could use SchemaBuilder.CreateMapIndexTable for the EmployeeSkill and SkillDescription tables.

Skrypt commented 2 years ago

Documentation it is ...

fasteddys commented 2 years ago

@Skrypt thanks for the guidance here... exactly what we were looking for... first its not always admins looking at dashboards. If you look at Kendo/SiteCore/ABP etc. they have business user facing dashboards in the apps. This is a very common scenario besides dataTables or Master-Detail Forms to view LOB transactional data.

"...I believe that Widgets shapes can be added from the Admin UI by using the "Templates" feature. Though, when it comes to consume a javascript library I generally build an entire module that builds these Widgets. Else, it is also totally doable from the admin UI if you don't need to create handlers and drivers for your custom shapes..." (Awesome advice) 👍

I also see how your migration builder is adding the schema (similar to EF), which has helped us. 👍

Use case Since we already had another app with dashboards and charts, where we already had a simple ViewComponent(previously Partials) tried to easily convert/port over that code and failed.. because of the internals on how to wire it up.

Scaffolder: We tried to scaffold a new module and port over the code - we were unable to follow how to achieve this even from the documentation (I have not see the latest), but if it could have some pictures describing the layers/shapes imagine a lasagna or a stack to visually depict what goes where and the sequence of steps or even a sample that would help.

Skrypt commented 2 years ago

I think I would start by analyzing the OrchardCore.Demo module

https://github.com/OrchardCMS/OrchardCore/tree/main/src/OrchardCore.Modules/OrchardCore.Demo

And then I think you need to learn the differences between "Content Type", "Content Part", "Content Field", Shapes, Drivers, Handlers.

Here, a driver is something similar to a controller but for a shape. And a shape is kind of a dynamic ViewModel but it is also more than just that; it is used also as views templates. You need to think about the fact that Orchard Core uses composition to create its runtime data models and there is some dynamic typing involved because of that. So, we don't use standard controllers for these because of the dynamic nature of shapes. We have handlers too that can be used to morph the data based on specific predefined events in the Orchard Core framework.

I think you need to take a simple module like the OrchardCore.Demo one and try different things. Look at other modules to see how things are done when needing more advanced things. That's the best way to learn. Else, there is the documentation for really specific things but maybe we are missing simple tutorials.

There is also this repository :

https://github.com/OrchardCMS/OrchardCore.Samples

But I think it needs an update.

hishamco commented 2 years ago

In terms of graphs and chart I think Lombiq already did a module for Chart Js. @Piedone correct me if I'm wrong

Piedone commented 2 years ago

Yes, the Chart.js module. Thanks for pointing out Hisham.

fasteddys commented 2 years ago

@Piedone I was able to download an build and see the chart component very helpful 👍 missing some steps on making it functional in a module for us.

Usecase: The user selected the widget and wants to connect materials or shift dataset to it, can you please share some info on how are the backend DataSources/Tables exposed/discovered to the widget and how does the Module know about the widget?

Use case sample: For e.g. we have a small manufacturing/recycling warehouse sample.


For e.g. we have several Tables, and taking these two for e.q:

MaterialsTable [id, vendor, materialtype (metal/plastic/glass), WeightDropOff, MeltingCompleted, ShiftHours, TotalHours ]

ShiftsTable [id, ShiftType, DateTimeStarted,DateTimeCompleted, WeightCompleted, NumberOfWorkers, TotalCost ]

ChartView... Manager wants to view in the graph widget

We can render the graph, and we see your datasets definition, but do we configure this so user can pick each of those to view.

Please confirm we are doing right steps:

We made a widget and a module but dont know what to do to connect the two.

codegen module Manfacturing /IncludeInSolution:true


From your ChartJsDataSet base class we created new dataSets

public class MaterialsTableDataSet : ChartJsDataSet
{
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public IEnumerable<string> materialtype { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public IEnumerable<decimal> TotalHours { get; set; }
    //... other props
}

public class ShiftsTableDataSet : ChartJsDataSet
{
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public IEnumerable<string> ShiftType { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public IEnumerable<decimal> TotalCost  { get; set; }
    //... other props
}

sarahelsaig commented 2 years ago

EDIT: We have a sample project now, see here.

Hi @fasteddys ,

The Chart.JS module is focused on the front-end side of things. You have to access data from the DB separately. It sounds like you already have a DB with this data to be displayed, so doing a database-first setup with Entity Framework Core might be the most appropriate.

You shouldn't inherit from ChartJsDataSet. If you haven't yet, create a separate model you use to get data from your DB table. Then create a view-model for your widget that contains these properties:

    public IEnumerable<string>> ChartLabels { get; set; }
    public IEnumerable<ChartJsDataSet> ChartDataSets { get; set; }
    public object ChartOptions { get; set; }

Your widget's driver should fetch the DB rows, convert them into data series according to your business logic and then initialize the viewmodel that contains the above properties like this:

    // Fetch from DB, convert as needed. If you need his to be user customizable, attach that information to
    // your widget's content item so it can be accessed by your logic here.
    var shifts = ...;

    model.ChartLabels = shifts.Select(shift => shift.DateTimeStarted.ToString());
    model.ChartDataSets = new List<ChartJsDataSet>
    {
        new()
        {
            Label = "Total Cost",
            Data = shifts.Select(shift => shift.TotalCost),
        },
    };
    // Options can be PascalCase, but otherwise same as https://www.chartjs.org/docs/2.9.4/configuration/#global-configuration
    model.ChartOptions = new
    {
        Layout = new Layout(50, 10, 10, 10),
    };

Finally include in your widget's template:

        <chart labels="@viewModel.ChartLabels"
               datasets="@viewModel.ChartDataSets"
               options="@viewModel.ChartOptions"></chart>
fasteddys commented 2 years ago

Much love 😄 to your help @DAud-IcI thanks to your help the mud is clearing up some.

Samples: don't show drivers When I study/follow/tried to search for driver inside the chart Widget sample and module sample both were empty https://github.com/Lombiq/Orchard-Chart.js/search?q=driver https://github.com/OrchardCMS/OrchardCore.Samples/search?q=driver


So, from your help, I understand this I should code it up like a regular MVC app, and let user switch between datasets in frontend, and in view switch in JS code, I am assuming when I pass more than one in the model, the driver will make it available to the chartJSWidget

model.ChartDataSets = new List<ChartJsDataSet>
{
    new()
    {
        Label = "Total Cost",
        Data = shifts.Select(shift => shift.TotalCost),
    },
};

// I added these and planning to let user select and should I change it in the frontend, like a regular MVC view?
model.MaterialsDataSets = new List<ChartJsMaterialsDataSet>
{
    new()
    {
        Label = "Total Time",
        Data = Materials.Select(materials=> materials.TotalHours),
    },
};

Documentation help section: is confusing it shows drivers inside modules, but the ChartJs is a UI widget?? so are they the same thing, how do they get linked/wired up?? I added a suggestion below...

I was trying to follow this Orchard help so I could build the driver for the widget

image

Please correct me

Maybe the clue is the content part behind a widget, yes?? public class MyChartDriver : ContentPartDriver<MyChartWidget> ?


Humble Suggestion

While, they have a good effort and nice amount of documentation, a picture to show the architecture & composition would really help clear up the relationships/inheritance/structure and MODULE<-> Widgetcomposition/aggregates.

For e.g. a layered or relationship image on how to build up to the widget from a module! image

sarahelsaig commented 2 years ago

Samples: don't show drivers

Yes, my sample only included the Chart specific parts to keep it simple. Drivers are kind of like controllers for content items, and a widget is just a specialized content item. Check out the "Content Item and Content Part development" section of our training demo (or specifically the driver here) if you've never worked with them before.

Documentation help section:

Disregard that documentation, it's for the legacy "Orchard CMS" (also known as O1). It's an older CMS made for .Net Framework and many concepts don't translate to Orchard Core so it will only get confusing.

So I assumed a Widget is just another independent module/project packaging on how code is organized?

A widget is a content type with a special stereotype value. They can be placed on the layout independently of the current page's content (in Admin Dashboard > Design > Widgets). A module can contain multiple widgets, but you have to define them like any other content type (see the training demo linked above). image

Humble Suggestion

I've tried, but I'm not graphic designer. oc

fasteddys commented 2 years ago

Thanks @DAud-IcI your last explanation gave me enough to break more stuff 😃 , appreciate your help 🥇 its so valuable