AgentSkillsCN

migrate-grid

使用neptune-grid与ag-Grid,将数据网格从传统MVC迁移到Angular。确保与旧版完全保持列的对应关系。

SKILL.md
--- frontmatter
name: migrate-grid
description: Migrate data grids from legacy MVC to Angular using neptune-grid and ag-Grid. Ensures complete column parity with legacy.
allowed-tools: [Read, Glob, Grep, Edit, Write, Bash(dotnet build:*), Bash(npm run gen-model:*)]
argument-hint: <EntityName> <GridName>

Migrate Grid Skill

When the user invokes /migrate-grid <EntityName> <GridName>:

Overview

This skill guides the migration of data grids from legacy MVC views to Angular using the neptune-grid component, ensuring complete column parity with the legacy implementation.


1. Analyze Legacy Grid Implementation

First, thoroughly examine the legacy MVC grid:

Find Legacy Grid Views

  • Index views: Neptune.WebMvc/Views/{Entity}/Index.cshtml
  • Detail views: Neptune.WebMvc/Views/{Entity}/Detail.cshtml
  • Partial views: Neptune.WebMvc/Views/{Entity}/*Grid*.cshtml
  • Shared partials: Neptune.WebMvc/Views/Shared/*Grid*.cshtml

Note: Neptune's legacy grids use DhtmlxGrid. Look for grid initialization patterns in the views.

Document Every Column

Create a column inventory table:

#HeaderField/ExpressionTypeLink?Filter?Notes
1NameTreatmentBMPNameTextYes, to detailYes-
2StatusStatusNameTextNoDropdown-
3DateCreatedDateDateNoDate rangeFormat: M/d/yyyy
4Area (ac)DelineationAreaDecimalNoNumber2 decimals
5Actions-ActionsYesNoEdit, Delete

Identify Data Source

  • Look for grid data controller actions
  • Check for any complex joins or calculations
  • Note any spatial data transformations

2. Design Grid Row DTO

Naming Convention

  • Grid DTOs: {Entity}GridDto
  • Context-specific grid DTOs: {Entity}{Context}GridDto
  • Examples:
    • TreatmentBMPGridDto — main BMP grid
    • ProjectTreatmentBMPGridDto — BMPs on project detail page

DTO Structure

csharp
// In Neptune.Models/DataTransferObjects/{Entity}GridDto.cs
namespace Neptune.Models.DataTransferObjects;

public class EntityGridDto
{
    // Primary key for row identification
    public int EntityID { get; set; }

    // Simple text fields
    public string Name { get; set; }
    public string Description { get; set; }

    // Related entity names (denormalized for grid display)
    public string? JurisdictionName { get; set; }
    public string? StatusName { get; set; }

    // Formatted/calculated fields
    public decimal? TotalArea { get; set; }
    public DateTime? CreatedDate { get; set; }

    // Boolean fields
    public bool IsActive { get; set; }
}

3. Create Extension Method

The AsGridDto() extension method is defined in Neptune.EFModels/Entities/Generated/ExtensionMethods/:

csharp
public static EntityGridDto AsGridDto(this Entity entity)
{
    return new EntityGridDto
    {
        EntityID = entity.EntityID,
        Name = entity.Name,
        Description = entity.Description,
        JurisdictionName = entity.StormwaterJurisdiction?.Organization?.OrganizationName,
        StatusName = entity.Status?.StatusDisplayName,
        TotalArea = entity.DelineationArea,
        CreatedDate = entity.CreateDate,
        IsActive = entity.IsActive ?? false
    };
}

4. Create Static Helper Method

csharp
// In Neptune.EFModels/Entities/{PluralEntity}.cs
public static class Entities
{
    public static List<EntityGridDto> ListAsGridDto(NeptuneDbContext dbContext)
    {
        return dbContext.Entities
            .AsNoTracking()
            .Include(x => x.StormwaterJurisdiction)
                .ThenInclude(j => j.Organization)
            .Include(x => x.Status)
            .Select(x => x.AsGridDto())
            .ToList();
    }

    // For filtered grids (e.g., entities for a specific parent)
    public static List<EntityGridDto> ListByParentIDAsGridDto(
        NeptuneDbContext dbContext, int parentID)
    {
        return dbContext.Entities
            .AsNoTracking()
            .Where(x => x.ParentID == parentID)
            .Include(x => x.StormwaterJurisdiction)
                .ThenInclude(j => j.Organization)
            .Select(x => x.AsGridDto())
            .ToList();
    }
}

5. Add API Endpoint

csharp
// In Neptune.API/Controllers/{Entity}Controller.cs
[HttpGet]
[UserViewFeature]
public ActionResult<List<EntityGridDto>> List()
{
    var entities = Entities.ListAsGridDto(DbContext);
    return Ok(entities);
}

// For child grids on parent detail pages
[HttpGet("{parentID}/entities")]
[UserViewFeature]
public ActionResult<List<EntityGridDto>> ListByParent([FromRoute] int parentID)
{
    var entities = Entities.ListByParentIDAsGridDto(DbContext, parentID);
    return Ok(entities);
}

6. Create Angular Column Definitions

Import ag-Grid Helper

typescript
import { AgGridHelper } from "src/app/shared/helpers/ag-grid-helper";
import { ColDef } from "ag-grid-community";
import { EntityGridDto } from "src/app/shared/generated/model/entity-grid-dto";

public columnDefs: ColDef<EntityGridDto>[] = [];

ngOnInit(): void {
    this.columnDefs = this.createColumnDefs();
}

Column Definition Methods Reference

MethodUse CaseExample
createBasicColumnDefText, nested object fieldsName, Description
createLinkColumnDefSingle link to another entityBMP Name -> BMP Detail
createMultiLinkColumnDefArray of linksTags, Programs
createDateColumnDefDates with formatCreated Date
createDecimalColumnDefNumbers with decimalsArea (2 decimal places)
createCurrencyColumnDefMoney valuesAmount ($X,XXX)
createBooleanColumnDefYes/No valuesIs Active
createActionsColumnDefEdit/Delete buttonsRow actions

Column Definition Examples

typescript
private createColumnDefs(): ColDef<EntityGridDto>[] {
    return [
        // Link column
        AgGridHelper.createLinkColumnDef("Name", "Name", "EntityID", {
            InRouterLink: "/entities/"
        }),

        // Basic text column
        AgGridHelper.createBasicColumnDef("Jurisdiction", "JurisdictionName", {
            CustomDropdownFilterField: "JurisdictionName"
        }),

        // Date column
        AgGridHelper.createDateColumnDef("Start Date", "StartDate", "M/d/yyyy"),

        // Decimal column
        AgGridHelper.createDecimalColumnDef("Area (ac)", "TotalArea", {
            MaxDecimalPlacesToDisplay: 2
        }),

        // Boolean column with dropdown filter
        AgGridHelper.createBooleanColumnDef("Active", "IsActive", {
            CustomDropdownFilterField: "IsActive",
        }),

        // Actions column
        AgGridHelper.createActionsColumnDef((params) => ({
            items: [
                {
                    name: "Edit",
                    icon: "Edit",
                    callback: () => this.openEditModal(params.data)
                },
                {
                    name: "Delete",
                    icon: "Delete",
                    callback: () => this.confirmDelete(params.data)
                }
            ]
        }))
    ];
}

Dropdown Filters

Always use CustomDropdownFilterField for columns with a fixed set of values:

Column TypeUse Dropdown Filter?
Boolean (Yes/No)Yes
Status/StageYes
Type/CategoryYes
Linked entity nameYes
Free-form textNo
NumbersNo
DatesNo

Column Alignment

Right-aligned columns (handled automatically by helper methods):

  • Numbers (createDecimalColumnDef)
  • Dates (createDateColumnDef)
  • Currency (createCurrencyColumnDef)

Left-aligned (default): text columns


7. Angular Template Pattern

Basic Grid

html
<div class="card">
    <div class="card-header"><span class="card-title">Entities</span></div>
    <div class="card-body">
        <neptune-grid
            [rowData]="entities$ | async"
            [columnDefs]="columnDefs"
            [downloadFileName]="'entities'">
        </neptune-grid>
    </div>
</div>

Grid with Height and Custom Options

html
<neptune-grid
    [rowData]="entities$ | async"
    [columnDefs]="columnDefs"
    [height]="'400px'"
    [downloadFileName]="'entities'">
</neptune-grid>

8. Grid Component Input Reference

InputTypeDefaultDescription
rowDataany[]-Grid data array
columnDefsColDef[]-Column definitions
heightstring'500px'Grid height
downloadFileNamestring'grid-data'CSV export filename
hideDownloadButtonbooleanfalseHide CSV download button
hideGlobalFilterbooleanfalseHide search box
sizeColumnsToFitGridbooleanfalseFit columns to grid width
paginationbooleanfalseEnable pagination
paginationPageSizenumber100Rows per page

9. Common Patterns

Multiple Grids on One Page

typescript
public bmpColumnDefs: ColDef<TreatmentBMPGridDto>[] = [];
public projectColumnDefs: ColDef<ProjectGridDto>[] = [];

ngOnInit(): void {
    this.bmpColumnDefs = this.createBMPColumnDefs();
    this.projectColumnDefs = this.createProjectColumnDefs();
}

Refreshing Grid Data

typescript
private refreshData$ = new Subject<void>();

this.entities$ = combineLatest([this.entityID$, this.refreshData$.pipe(startWith(undefined))]).pipe(
    switchMap(([id]) => this.entityService.listByParent(id)),
    shareReplay({ bufferSize: 1, refCount: true })
);

refreshGrid(): void {
    this.refreshData$.next();
}

10. Column Parity Checklist

Before considering migration complete, verify:

  • All legacy columns are present in Angular grid
  • Column headers match (or are intentionally improved)
  • Data formats match:
    • Dates display in same format (M/d/yyyy)
    • Currency displays with proper formatting ($X,XXX)
    • Decimals show correct precision
    • Numbers have comma separators (1,234,567)
    • Booleans show Yes/No
  • Column alignment:
    • Numbers are right-aligned
    • Dates are right-aligned
    • Currency is right-aligned
    • Text is left-aligned (default)
  • Links navigate to correct routes
  • Dropdown filters configured for lookup columns
  • Column sort works correctly
  • Actions column has all required actions

11. Migration Checklist

  • Documented all legacy grid columns
  • Created GridDto with all required fields
  • Created/updated extension method (AsGridDto)
  • Created static helper method(s)
  • Added API endpoint(s)
  • Ran dotnet build Neptune.API to generate swagger.json
  • Ran npm run gen-model to generate TypeScript models
  • Created column definitions using ag-Grid helper
  • Added neptune-grid component to template
  • Verified column parity with legacy grid
  • Verified filtering works
  • Verified sorting works
  • Verified links navigate correctly
  • Verified CSV export works