Import To Planner is a single-purpose Blazor application for importing CSV task lists into Microsoft Planner through a safe, operator-led workflow.
Quick links:
- Public docs: https://docs.importplanner.app
- Contributor guide: CONTRIBUTING.md
- Developer quick-start: docs-internal/developer-quickstart.md
- Architecture and governance: .specify/memory/constitution.md
Import To Planner converts CSV task lists into Planner tasks using a controlled workflow that separates validation, preview, and execution.
Primary purpose:
- Provide a controlled CSV-to-Planner import workflow with validation, preview, and explicit execution confirmation.
- Support hosted and self-hosted authority configurations through
AzureAd:HomeTenantIdwhile keeping one Graph-backed runtime path. - Preserve self-hosting as a permanent supported delivery mode even when hosted or commercial capabilities are added.
The workflow preserves operational safeguards:
- Select container.
- Select plan.
- Upload CSV and import options.
- Validate and preview.
- Confirm execution and review results.
The app now has one supported runtime path:
- Microsoft Graph planner operations with table-backed tenant metadata and blob-backed Data Protection persistence.
- Authentication remains authority-driven through
AzureAd:HomeTenantId(multipleor a specific tenant value).
Public end-user documentation is available at https://docs.importplanner.app.
This site covers onboarding, CSV format, import workflow, troubleshooting, FAQ, privacy and security, plus secondary self-hosted guidance.
Repository policy keeps self-hosting as a first-class supported path. Hosted or commercial features are expected to be additive rather than prerequisites for running the software from your own tenant and infrastructure.
Documentation split:
docs/: public end-user and operator guidance published via GitHub Pagesdocs-internal/: internal contributor, engineering, and operational guidance
- Row-level and file-level CSV validation before write actions.
- Dry-run preview separated from execution.
- Explicit confirm-and-execute flow with stale-preview protection.
- Existing-task matching by task name (
already existsoutcome). - Partial-success execution handling with a single retry for transient row failures.
- Execution reporting for created items, skipped or reused items, manual actions, and errors.
- Searchable container and plan selectors for larger tenants.
- Clearer step-state guidance, CSV field expectations, and concise manual follow-up messaging.
- Regression coverage for startup validation, authority handling, and single-path planner registration.
- Optional commercial account mode with first sign-in account creation, profile page, delete-and-restore lifecycle, and retention sweep support.
Primary feature sources:
- specs/001-import-planner-csv/spec.md
- specs/001-import-planner-csv/contracts/import-workflow-contract.md
- specs/002-ui-ux-redesign/spec.md
- specs/003-align-clean-architecture/spec.md
- specs/004-add-multitenant-hosting/spec.md
- specs/005-simplify-graph-path/spec.md
- specs/006-refine-import-guidance/spec.md
- Platform and language:
- .NET SDK 10.0.100
- C# 14
- ASP.NET Core Blazor Web App
- UI:
- MudBlazor 9.5.0
- Core libraries:
- CsvHelper 33.1.0
- Microsoft.Graph 6.1.0
- Microsoft.Kiota.Abstractions 2.0.0
- Microsoft.Identity.Web 4.10.0
- Microsoft.Identity.Web.UI 4.10.0
- Hosting and observability:
- Aspire AppHost SDK 13.5.3
- OpenTelemetry 1.15.x packages
- Shared service defaults for resilience and telemetry
- Testing:
- xUnit v3 4.0.0
- NSubstitute
- bUnit 2.9.0
- Microsoft.NET.Test.Sdk 18.9.0
Primary version sources:
- global.json
- Directory.Packages.props
- ImportToPlanner.slnx
- src/ImportToPlanner.AppHost/ImportToPlanner.AppHost.csproj
The solution follows layered Clean Architecture. Domain and Application own policy, Infrastructure provides adapters, and Web owns presentation and workflow state.
flowchart LR
Web[ImportToPlanner.Web\nBlazor UI, presenters, workflow coordination] --> App[ImportToPlanner.Application\nUse cases and contracts]
Infra[ImportToPlanner.Infrastructure.Graph\nCSV parser and planner gateways] --> App
App --> Domain[ImportToPlanner.Domain\nBusiness concepts]
Web --> Defaults[ImportToPlanner.ServiceDefaults\nTelemetry and resilience defaults]
AppHost[ImportToPlanner.AppHost\nAspire AppHost] --> Web
Projects in solution:
src/ImportToPlanner.Domain: domain entities and business concepts.src/ImportToPlanner.Application: use-case orchestration and boundary contracts.src/ImportToPlanner.Infrastructure.Graph: CSV parsing and planner gateway implementations.src/ImportToPlanner.Web: Blazor UI, authentication entry behaviour, and stepped workflow.src/ImportToPlanner.ServiceDefaults: shared service defaults for resilience and telemetry.
Architecture and governance references:
- Self-hosted single-tenant mode: recommended when you want to run the app for your own organisation.
- Hosted shared multi-tenant mode: supported for shared-service deployments across approved work or school tenants.
Self-hosting remains a permanent supported delivery path for this repository.
- Commercial mode is controlled by
Features:CommercialMode:Enabled. - When enabled, the app shows a commercial sign-in gate for signed-out visitors and creates an account keyed by
TenantId+UserIdon first sign-in. - Profile management is available at
/profile, including delete and restore behaviour during the retention period. - When commercial mode is disabled, self-hosted sign-in behaviour remains unchanged and commercial persistence is bypassed.
- Run with
AzureAd:HomeTenantId=multiplewhen you need shared-organisations authority behaviour. - Run with
AzureAd:HomeTenantId=<tenant-id-or-domain>when you need single-tenant authority behaviour. - Use Aspire for both paths so storage wiring remains consistent with production.
Recommended Entra setup:
- Hosted shared mode: use a dedicated multitenant app registration, set
AzureAd:TenantIdto the registration's home tenant, and setAzureAd:HomeTenantId=multiple. - Self-hosted mode: use a tenant-owned single-tenant app registration, set
AzureAd:TenantIdto that registration tenant, and setAzureAd:HomeTenantIdto that same tenant. - Do not treat the Azure deployment tenant ID as the hosted runtime authority toggle. Runtime authority is controlled by
AzureAd:HomeTenantId.
For the full internal setup guide, including the recommended separate-registration strategy and troubleshooting for the "Selected user account does not exist in tenant" error, see docs-internal/entra-app-registration-setup.md.
- .NET 10 SDK.
- Microsoft 365 account with Planner access.
- Entra ID app registration with required delegated permissions.
- Local configuration for
AzureAdand Graph settings. - Container runtime for local Azurite emulation when using Aspire.
- Optional local tooling:
- Aspire CLI for developer workflows and local orchestration.
- Node.js (LTS) for local JavaScript syntax checks used in CI.
- GitHub CLI for issue and pull request workflows.
If you prefer a containerised setup, GitHub Codespaces is supported through .devcontainer/devcontainer.json.
dotnet restore ImportToPlanner.slnx
dotnet format ImportToPlanner.slnx --no-restore --verify-no-changes --verbosity minimal
dotnet build ImportToPlanner.slnx
dotnet test ImportToPlanner.slnx
git ls-files '*.js' | xargs -n1 node --checkThe repository includes ready-made Aspire launch profiles in .vscode/launch.json:
Aspire: Run (Single Tenant - In Memory)- recommended first run for contributors and local evaluation.Aspire: Run (Single Tenant + Graph)- self-hosted single-tenant sign-in and real Planner calls.Aspire: Run (Multi Tenant + Hosted Storage)- hosted shared multi-tenant verification with local hosted-storage emulation.
The first profile is deliberately ordered first so a new VS Code user lands on the simplest path. Profile names are historical labels; all supported paths now use Graph plus storage-backed services.
If you prefer the CLI, run the baseline validation commands above and then start the AppHost with aspire run.
Aspire is the recommended path because it wires storage, blobs, and tables automatically. If you need to run the Web project directly, provide equivalent connection settings and AzureAd secrets first:
dotnet user-secrets set "AzureAd:TenantId" "<app-registration-tenant-id-or-domain>" --project src/ImportToPlanner.Web
dotnet user-secrets set "AzureAd:HomeTenantId" "multiple" --project src/ImportToPlanner.Web
dotnet user-secrets set "AzureAd:ClientId" "<client-id>" --project src/ImportToPlanner.Web
dotnet run --project src/ImportToPlanner.Web/ImportToPlanner.Web.csprojExpected behaviour:
- Unauthenticated users are challenged through Microsoft Identity.
- Planner data comes from Microsoft Graph.
- Storage-backed services use the configured connection settings.
Use this path when you want sign-in constrained to one tenant:
dotnet user-secrets set "AzureAd:TenantId" "<tenant-id-or-domain>" --project src/ImportToPlanner.Web
dotnet user-secrets set "AzureAd:HomeTenantId" "<tenant-id-or-domain>" --project src/ImportToPlanner.Web
dotnet run --project src/ImportToPlanner.Web/ImportToPlanner.Web.csprojUse a tenant-owned single-tenant app registration for this path. Keep the redirect URI aligned with the app origin you are running, always ending with /signin-oidc, and grant the delegated Graph permissions listed in src/ImportToPlanner.Web/appsettings.json.
Expected behaviour:
- Unauthenticated sessions are redirected to sign-in.
- Container and plan data are loaded from Microsoft Graph.
- Sign-in remains single-tenant.
See src/ImportToPlanner.Web/appsettings.json for the full configuration shape, including AzureAd, certificate, and Graph scope placeholders, and see docs-internal/microsoft-graph-guidelines.md for implementation guidance.
Aspire is the recommended developer path because the AppHost and launch profiles keep authority and storage wiring explicit and consistent.
aspire start --isolated
aspire describe
aspire logs web
aspire stopNotes:
- The AppHost always starts
storage,blobs,tables, andweb. - A container runtime is needed for local Azurite emulation.
- For deeper developer guidance, see docs-internal/developer-quickstart.md.
src/
ImportToPlanner.Application/
ImportToPlanner.Domain/
ImportToPlanner.Infrastructure.Graph/
ImportToPlanner.ServiceDefaults/
ImportToPlanner.Web/
tests/
ImportToPlanner.Tests/
ImportToPlanner.Web.Tests/
docs/
docs-internal/
specs/
src/ImportToPlanner.AppHost/
ImportToPlanner.slnx
Repository areas:
src/: production projects.tests/: unit, integration-style, and Blazor UI tests.docs/: public-facing documentation.docs-internal/: internal engineering guidance.specs/: Spec Kit artefacts (specs, plans, tasks, quickstarts, contracts).
This repository uses specification-led delivery and explicit governance:
- Feature requirements, plans, and tasks live in
specs/. - Repository-wide agent policy and skill delegation are in AGENTS.md.
- Architecture governance is in .specify/memory/constitution.md.
- Operational policies are in docs-internal/engineering-policies.md.
Contribution flow summary:
- Branch from
main. - Keep the change focused to one logical concern.
- Preserve linear history (rebase or squash; no merge commits).
- Keep CI green before requesting review.
- Update tests and relevant docs with behaviour or setup changes.
See CONTRIBUTING.md for full pull request and review-thread guidance.
Key standards:
- Use UK English in user-facing and contributor-facing wording.
- Preserve dependency direction and layer boundaries across Web, Application, Domain, and Infrastructure.
- Keep provider-specific concepts (Graph, Kiota, transport details) in adapter layers.
- Prefer MudBlazor components and parameters before custom CSS or HTML workarounds.
- Use async end-to-end for I/O and avoid blocking calls.
- Avoid exposing secrets, certificate values, or tenant-sensitive identifiers.
Standards references:
- AGENTS.md
- .github/instructions/blazor-csharp.instructions.md
- .github/instructions/csharp-clean-architecture.instructions.md
- docs-internal/microsoft-graph-guidelines.md
Test projects:
tests/ImportToPlanner.Tests: application and infrastructure tests.tests/ImportToPlanner.Web.Tests: Blazor UI and workflow tests.
Run all tests:
dotnet test ImportToPlanner.slnxCollect coverage locally:
dotnet tool install -g dotnet-coverage
dotnet-coverage collect -f cobertura -o coverage.cobertura.xml dotnet test ImportToPlanner.slnxTesting expectations include regression coverage for changed behaviour, startup validation, and authority-specific auth handling. See tests/README.md and docs-internal/engineering-policies.md. Architecture rules that tests must satisfy live in .specify/memory/constitution.md.
Contributions are welcome, but scope remains intentionally focused.
- Read CONTRIBUTING.md before opening a pull request.
- Follow CODE_OF_CONDUCT.md.
- Keep pull requests small and focused.
- Ensure CI checks pass before requesting review.
- Reply to review comments in-thread.
- Update contributor setup docs when development behaviour changes.
Recommended contributor flow:
- Create a branch from
main. - Run restore, format, build, test, and JavaScript syntax checks locally.
- Make the smallest change that satisfies the requirement.
- Update tests and documentation alongside behaviour changes.
- Open a PR targeting
mainwith a clear explanation of what changed and why.
- specs/004-add-multitenant-hosting/quickstart.md
- docs-internal/microsoft-graph-guidelines.md
- docs-internal/aspire-production-readiness.md
- tests/README.md
- docs/README.md
- docs-internal/README.md
- docs-internal/roadmap-and-limitations.md
- specs/001-import-planner-csv/quickstart.md
- specs/002-ui-ux-redesign/quickstart.md
- specs/003-align-clean-architecture/quickstart.md
This project is licensed under the MIT Licence.