Compare commits
90
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59f9bf290d | ||
|
|
b6b8f54c65 | ||
|
|
50139ee506 | ||
|
|
62947b2862 | ||
|
|
1748a08f7b | ||
|
|
3264d58f13 | ||
|
|
b6be468b3a | ||
|
|
3ec88aab65 | ||
|
|
da43086017 | ||
|
|
d62fe5e315 | ||
|
|
852192a5d2 | ||
|
|
f4498c3d05 | ||
|
|
b045b33049 | ||
|
|
32a4fbebd5 | ||
|
|
cc2f1df78e | ||
|
|
9b2fd1253b | ||
|
|
024697f3bf | ||
|
|
d8b76575ca | ||
|
|
5b00312158 | ||
|
|
d920e93d1e | ||
|
|
feba51a49d | ||
|
|
853c1acf7e | ||
|
|
3f10bd8c21 | ||
|
|
b13eb8aaf8 | ||
|
|
275478066e | ||
|
|
b01def0872 | ||
|
|
b106305167 | ||
|
|
103631de15 | ||
|
|
1b19ca7cdb | ||
|
|
0ad020f0ce | ||
|
|
fc1455e0d8 | ||
|
|
793534c627 | ||
|
|
15a8b4066a | ||
|
|
38f6e2b0da | ||
|
|
14f60a108a | ||
|
|
10518ed454 | ||
|
|
375a0b4388 | ||
|
|
6300ccc211 | ||
|
|
5b6f544d2a | ||
|
|
a1379f13a0 | ||
|
|
38e9781fc5 | ||
|
|
dfc6ead6d5 | ||
|
|
30a431f870 | ||
|
|
e932fa5404 | ||
|
|
3579c4ac9e | ||
|
|
671751b618 | ||
|
|
3b6e4a96f1 | ||
|
|
83e580c3a7 | ||
|
|
31ee35e721 | ||
|
|
cf0f3f1380 | ||
|
|
b4528cf963 | ||
|
|
cb1ec988ce | ||
|
|
31c3f5ae0c | ||
|
|
15643135ba | ||
|
|
297ceaf0aa | ||
|
|
8e87f2260c | ||
|
|
927e4d8cad | ||
|
|
cd71e5666a | ||
|
|
c38faec74b | ||
|
|
c86a6fa8e5 | ||
|
|
1e521760df | ||
|
|
1ccb5cc09e | ||
|
|
12ca71558a | ||
|
|
8b759cf3a4 | ||
|
|
25fffd4173 | ||
|
|
29c0151460 | ||
|
|
beb60065bb | ||
|
|
cc9c33bfe6 | ||
|
|
6edffd9f08 | ||
|
|
7e57396960 | ||
|
|
981f173a79 | ||
|
|
83fe87ca88 | ||
|
|
432dda8c47 | ||
|
|
ae078bf96b | ||
|
|
6455f96e51 | ||
|
|
a9fc88d7e6 | ||
|
|
76e2103551 | ||
|
|
340ae6c474 | ||
|
|
7495c3c7b2 | ||
|
|
449c94f08d | ||
|
|
83b92666ac | ||
|
|
b7bf30744a | ||
|
|
9e97f6028b | ||
|
|
f9496e8067 | ||
|
|
8ba3a4d926 | ||
|
|
7fe6971a61 | ||
|
|
7b10d39d66 | ||
|
|
9ef2b37be4 | ||
|
|
516d62a348 | ||
|
|
97b3023e14 |
@@ -61,7 +61,7 @@ This guide describes each step to make your first contribution:
|
||||
## Further contribution guides
|
||||
|
||||
- [Before you start](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-before-you-start.md)
|
||||
- [Finding your first issue: Up for grabs](contributing-first-issue.md)
|
||||
- [Contributing to the new backoffice](https://docs.umbraco.com/umbraco-backoffice/)
|
||||
- [Unwanted changes](contributing-unwanted-changes.md)
|
||||
- [Other ways to contribute](contributing-other-ways-to-contribute.md)
|
||||
|
||||
@@ -4,11 +4,11 @@ contact_links:
|
||||
url: https://github.com/umbraco/Umbraco-CMS/discussions/new?category=features-and-ideas
|
||||
about: Start a new discussion when you have ideas or feature requests, eventually discussions can turn into plans
|
||||
- name: ⁉️ Support Question
|
||||
url: https://our.umbraco.com
|
||||
url: https://forum.umbraco.com
|
||||
about: This issue tracker is NOT meant for support questions. If you have a question, please join us on the forum.
|
||||
- name: 📖 Documentation Issue
|
||||
url: https://github.com/umbraco/UmbracoDocs/issues
|
||||
about: Documentation issues should be reported on the Umbraco documentation repository.
|
||||
- name: 🔐 Security Issue
|
||||
url: https://umbraco.com/about-us/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
url: https://umbraco.com/trust-center/security-and-umbraco/how-to-report-a-vulnerability-in-umbraco/
|
||||
about: Discovered a Security Issue in Umbraco?
|
||||
|
||||
+1
-1
@@ -4,8 +4,8 @@
|
||||
[](https://www.nuget.org/packages/Umbraco.Cms)
|
||||
[](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=301)
|
||||
[](CONTRIBUTING.md)
|
||||
[](https://forum.umbraco.com)
|
||||
[](https://discord.gg/umbraco)
|
||||
[](https://discord-chats.umbraco.com)
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
<PackageVersion Include="Asp.Versioning.Mvc" Version="8.1.0" />
|
||||
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
|
||||
<PackageVersion Include="Dazinator.Extensions.FileProviders" Version="2.0.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.0" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.0" />
|
||||
<PackageVersion Include="Examine" Version="3.7.1" />
|
||||
<PackageVersion Include="Examine.Core" Version="3.7.1" />
|
||||
<PackageVersion Include="HtmlAgilityPack" Version="1.11.74" />
|
||||
<PackageVersion Include="JsonPatch.Net" Version="3.1.1" />
|
||||
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
|
||||
@@ -100,4 +100,4 @@
|
||||
<!-- Examine.Lucene brings in a vulnerable version of Lucene.Net.Replicator -->
|
||||
<PackageVersion Include="Lucene.Net.Replicator" Version="4.8.0-beta00017" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
Third-Party Notices
|
||||
===================
|
||||
|
||||
This file contains notices and attributions for third-party software used in the Umbraco CMS project.
|
||||
|
||||
It is not a license and does not grant any rights to use the third-party software.
|
||||
|
||||
Umbraco CMS is licensed under the MIT License, which can be found in the LICENSE file.
|
||||
|
||||
---
|
||||
|
||||
@openid/AppAuth-JS: An OpenID Connect and OAuth 2.0 client library for JavaScript
|
||||
|
||||
URL: https://github.com/openid/AppAuth-JS
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2017 Google Inc.
|
||||
|
||||
---
|
||||
|
||||
AutoFixture: Write maintainable unit tests, faster
|
||||
|
||||
URL: https://github.com/AutoFixture/AutoFixture
|
||||
License: MIT License
|
||||
Copyright: 2013 Mark Seemann
|
||||
|
||||
---
|
||||
|
||||
Asp.Versioning.Mvc: A library for ASP.NET Core versioning
|
||||
|
||||
URL: https://github.com/dotnet/aspnet-api-versioning
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and contributors
|
||||
|
||||
---
|
||||
|
||||
Babel: A JavaScript compiler
|
||||
|
||||
URL: https://babeljs.io/
|
||||
License: MIT License
|
||||
Copyright: 2014-present Sebastian McKenzie and other contributors
|
||||
|
||||
---
|
||||
|
||||
BenchmarkDotNet: Powerful .NET library for benchmarking
|
||||
|
||||
URL: https://github.com/dotnet/BenchmarkDotNet
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
Bogus: A simple and sane data generator for populating objects that supports different locales.
|
||||
|
||||
URL: https://github.com/bchavez/Bogus
|
||||
License: MIT License
|
||||
Copyright: 2015 Brian Chavez
|
||||
|
||||
---
|
||||
|
||||
CommandLineParser: Terse syntax C# command line parser for .NET
|
||||
|
||||
URL: https://github.com/commandlineparser/commandline
|
||||
License: MIT License
|
||||
Copyright: 2005-2015 Giacomo Stelluti Scala & Contributors
|
||||
|
||||
---
|
||||
|
||||
cross-env: A CLI tool to set environment variables across platforms
|
||||
|
||||
URL: https://github.com/kentcdodds/cross-env
|
||||
License: MIT License
|
||||
Copyright: 2017 Kent C. Dodds
|
||||
|
||||
---
|
||||
|
||||
Dazinator.Extensions.FileProviders: A library for file provider extensions
|
||||
|
||||
URL: https://github.com/dazinator/Dazinator.Extensions.FileProviders
|
||||
License: MIT License
|
||||
Copyright: 2016 Darrell
|
||||
|
||||
---
|
||||
|
||||
DOMPurify: A DOM-only XSS sanitizer for HTML, MathML and SVG
|
||||
|
||||
URL: https://github.com/cure53/DOMPurify
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2025 Dr.-Ing. Mario Heiderich, Cure53
|
||||
|
||||
---
|
||||
|
||||
Element Internals Polyfill: A polyfill for the Element Internals API
|
||||
|
||||
URL: https://github.com/calebdwilliams/element-internals-polyfill
|
||||
License: MIT License
|
||||
Copyright: 2021 Caleb Williams
|
||||
|
||||
---
|
||||
|
||||
Eslint: A tool for identifying and reporting on patterns in JavaScript
|
||||
|
||||
URL: https://eslint.org/
|
||||
License: MIT License
|
||||
Copyright: OpenJS Foundation and other contributors
|
||||
|
||||
---
|
||||
|
||||
Examine: A search and indexing library for .NET
|
||||
|
||||
URL: https://github.com/Shazwazza/Examine
|
||||
License: Microsoft Public License (Ms-PL)
|
||||
Copyright: 2023 Shannon Deminick
|
||||
|
||||
---
|
||||
|
||||
Glob: A library for matching file paths using glob patterns
|
||||
|
||||
URL: https://github.com/isaacs/node-glob
|
||||
License: ISC License
|
||||
Copyright: 2009-2023 Isaac Z. Schlueter and Contributors
|
||||
|
||||
---
|
||||
|
||||
Globals: A library for managing global variables in JavaScript
|
||||
|
||||
URL: https://github.com/sindresorhus/globals
|
||||
License: MIT License
|
||||
Copyright: Sindre Sorhus
|
||||
|
||||
---
|
||||
|
||||
Html Agility Pack: An HTML parser for .NET
|
||||
|
||||
URL: https://html-agility-pack.net/
|
||||
License: MIT License
|
||||
Copyright: ZZZ Projects Inc.
|
||||
|
||||
---
|
||||
|
||||
ImageSharp: A cross-platform library for processing images in .NET
|
||||
|
||||
URL: https://github.com/SixLabors/ImageSharp
|
||||
License: Apache License, Version 2.0 under the Six Labors Split License
|
||||
Copyright: Six Labors
|
||||
|
||||
---
|
||||
|
||||
jsdiff: A JavaScript text differencing implementation
|
||||
|
||||
URL: https://github.com/kpdecker/jsdiff
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2009-2015 Kevin Decker <kpdecker@gmail.com>
|
||||
|
||||
---
|
||||
|
||||
JsonPatch.Net: A library for JSON Patch (RFC 6902) in .NET
|
||||
|
||||
URL: https://github.com/json-everything/json-everything
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
K4os.Compression.LZ4: A fast LZ4 compression library for .NET
|
||||
|
||||
URL: https://github.com/MiloszKrajewski/K4os.Compression.LZ4
|
||||
License: MIT License
|
||||
Copyright: 2017 Milosz Krajewski
|
||||
|
||||
---
|
||||
|
||||
Lit: A simple library for building fast, lightweight web components
|
||||
|
||||
URL: https://lit.dev
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2020 Google LLC. All rights reserved.
|
||||
|
||||
---
|
||||
|
||||
Lucide: Beautiful & consistent icons for the web
|
||||
|
||||
URL: https://lucide.dev/
|
||||
License: ISC License
|
||||
Copyright: 2013-2022 Cole Bemis
|
||||
Copyright: 2022 Lucide Contributors
|
||||
|
||||
---
|
||||
|
||||
Madge: A dependency graph generator for JavaScript
|
||||
|
||||
URL: https://github.com/pahen/madge
|
||||
License: MIT License
|
||||
Copyright: 2017 Patrik Henningsson
|
||||
|
||||
---
|
||||
|
||||
MailKit: A library for sending email in .NET
|
||||
|
||||
URL: https://github.com/jstedfast/MailKit
|
||||
License: MIT License
|
||||
Copyright: 2013-2024 .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
Markdown: A library for parsing and compiling Markdown
|
||||
|
||||
URL: https://github.com/hey-red/Markdown
|
||||
License: MIT License
|
||||
Copyright: 2018 red
|
||||
|
||||
---
|
||||
|
||||
marked: A markdown parser and compiler
|
||||
|
||||
URL: https://marked.js.org/
|
||||
License: MIT License
|
||||
Copyright: 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
Copyright: 2018+, MarkedJS (https://github.com/markedjs/)
|
||||
|
||||
---
|
||||
|
||||
Message Pack: The extremely fast MessagePack serializer for C#
|
||||
|
||||
URL: https://github.com/MessagePack-CSharp/MessagePack-CSharp
|
||||
License: MIT License
|
||||
Copyright: 2017 Yoshifumi Kawai and contributors
|
||||
|
||||
---
|
||||
|
||||
Miniprofiler: A mini profiler for .NET
|
||||
|
||||
URL: https://github.com/MiniProfiler/dotnet
|
||||
License: MIT License
|
||||
Copyright: .NET MiniProfiler Contributors
|
||||
|
||||
---
|
||||
|
||||
Monaco Editor: A browser-based code editor
|
||||
|
||||
URL: https://microsoft.github.io/monaco-editor/
|
||||
License: MIT License
|
||||
Copyright: 2016-present Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Moq: A mocking library for .NET
|
||||
|
||||
URL: https://github.com/moq/moq
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2007 Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
|
||||
|
||||
---
|
||||
|
||||
Mock Service Worker (MSW): A library for mocking API requests in JavaScript
|
||||
|
||||
URL: https://mswjs.io/
|
||||
License: MIT License
|
||||
Copyright: 2018–present Artem Zakharchenko
|
||||
|
||||
---
|
||||
|
||||
NCrontab: A cron schedule parser for .NET
|
||||
|
||||
URL: https://github.com/atifaziz/NCrontab
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2001 The OpenSymphony Group
|
||||
Copyright: 2008 Atif Aziz
|
||||
|
||||
---
|
||||
|
||||
Nerdbank.GitVersioning: A library for versioning .NET projects
|
||||
|
||||
URL: https://github.com/dotnet/Nerdbank.GitVersioning
|
||||
License: MIT License
|
||||
Copyright: .NET Foundation and Contributors
|
||||
|
||||
---
|
||||
|
||||
NJsonSchema: A JSON schema validator for .NET
|
||||
|
||||
URL: https://github.com/RicoSuter/NJsonSchema
|
||||
License: MIT License
|
||||
Copyright: 2022 Rico Suter
|
||||
|
||||
---
|
||||
|
||||
NPoco: A micro ORM for .NET
|
||||
|
||||
URL: https://github.com/schotime/NPoco
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Schotime
|
||||
|
||||
---
|
||||
|
||||
NUnit: A unit testing framework for .NET
|
||||
|
||||
URL: https://github.com/nunit/nunit
|
||||
License: MIT License
|
||||
Copyright: Charlie Poole, Rob Prouse and Contributors
|
||||
|
||||
---
|
||||
|
||||
Open Web Components: A set of standards and libraries for building web components
|
||||
|
||||
URL: https://open-wc.org/
|
||||
License: MIT License
|
||||
Copyright: 2018 open-wc
|
||||
|
||||
---
|
||||
|
||||
Openapi-ts: The OpenAPI to TypeScript codegen
|
||||
|
||||
URL: https://github.com/hey-api/openapi-ts
|
||||
License: MIT License
|
||||
Copyright: Hey API
|
||||
|
||||
---
|
||||
|
||||
OpenIddict: A simple and flexible OpenID Connect server for ASP.NET Core
|
||||
|
||||
URL: https://github.com/openiddict/openiddict-core
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Kévin Chalet
|
||||
|
||||
---
|
||||
|
||||
Playwright: A Node.js library to automate browser testing
|
||||
|
||||
URL: https://playwright.dev/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2025 Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Playwright-msw: A library to wrap Mock Service Worker with Playwright
|
||||
|
||||
URL: https://github.com/valendres/playwright-msw
|
||||
License: MIT License
|
||||
Copyright: 2022 Peter Weller
|
||||
|
||||
---
|
||||
|
||||
Prettier: An opinionated code formatter
|
||||
|
||||
URL: https://prettier.io/
|
||||
License: MIT License
|
||||
Copyright: James Long and contributors
|
||||
|
||||
---
|
||||
|
||||
Remark-gfm: A GitHub Flavored Markdown plugin for Remark
|
||||
|
||||
URL: https://github.com/remarkjs/remark-gfm
|
||||
License: MIT License
|
||||
Copyright: Titus Wormer
|
||||
|
||||
---
|
||||
|
||||
Rollup: A module bundler for JavaScript
|
||||
|
||||
URL: https://rollupjs.org/
|
||||
License: MIT License
|
||||
Copyright: 2015-present Rollup contributors
|
||||
|
||||
---
|
||||
|
||||
Rollup Plugins: A collection of Rollup plugins
|
||||
|
||||
URL: https://github.com/rollup/plugins
|
||||
License: MIT License
|
||||
Copyright: 2019-present Rollup Plugins contributors
|
||||
|
||||
---
|
||||
|
||||
Rollup-plugin-esbuild: A Rollup plugin for using esbuild
|
||||
|
||||
URL: https://github.com/egoist/rollup-plugin-esbuild
|
||||
License: MIT License
|
||||
Copyright: 2020 EGOIST
|
||||
|
||||
---
|
||||
|
||||
Rollup-plugin-import-css: A Rollup plugin for importing CSS files
|
||||
|
||||
URL: https://github.com/jleeson/rollup-plugin-import-css
|
||||
License: MIT License
|
||||
Copyright: 2020 Jacob Leeson
|
||||
|
||||
---
|
||||
|
||||
rxjs: Reactive Extensions for JavaScript
|
||||
|
||||
URL: https://rxjs.dev/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2015-present Ben Lesh <ben@benlesh.com>, Google, Inc., Netflix, Inc., Microsoft Corp., and contributors
|
||||
|
||||
---
|
||||
|
||||
Serilog: A diagnostic logging library for .NET
|
||||
|
||||
URL: https://github.com/serilog/serilog
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Serilog Contributors
|
||||
|
||||
---
|
||||
|
||||
Simple Icons: A set of SVG icons for popular brands
|
||||
|
||||
URL: https://simpleicons.org/
|
||||
License: CC0 1.0 Universal License
|
||||
Copyright: Simple Icons Contributors
|
||||
|
||||
---
|
||||
|
||||
Storybook: A UI component explorer for Web Components
|
||||
|
||||
URL: https://storybook.js.org/
|
||||
License: MIT License
|
||||
Copyright: 2024 Storybook
|
||||
|
||||
---
|
||||
|
||||
StyleCop.Analyzers: Analyzers for StyleCop
|
||||
|
||||
URL: https://github.com/DotNetAnalyzers/StyleCopAnalyzers
|
||||
License: MIT License
|
||||
Copyright: Tunnel Vision Laboratories, LLC
|
||||
|
||||
---
|
||||
|
||||
SVGO: A tool for optimizing SVG files
|
||||
|
||||
URL: https://svgo.dev/
|
||||
License: MIT License
|
||||
Copyright: Kir Belevich
|
||||
|
||||
---
|
||||
|
||||
Swashbuckle.AspNetCore: A library for generating Swagger documentation for ASP.NET Core APIs
|
||||
|
||||
URL: https://github.com/domaindrivendev/Swashbuckle.AspNetCore
|
||||
License: MIT License
|
||||
Copyright: 2016 Richard Morris
|
||||
|
||||
---
|
||||
|
||||
Tiny Glob: A tiny globbing library for Node.js
|
||||
|
||||
URL: https://github.com/terkelg/tiny-glob
|
||||
License: MIT License
|
||||
Copyright: 2018 Terkel
|
||||
|
||||
---
|
||||
|
||||
TinyMCE, version 6.x: A rich text editor for the web
|
||||
|
||||
URL: https://www.tiny.cloud/
|
||||
License: MIT License
|
||||
Copyright: 2022 Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
|
||||
---
|
||||
|
||||
Tiptap: A renderless rich-text editor for the web
|
||||
|
||||
URL: https://tiptap.dev/
|
||||
License: MIT License
|
||||
Copyright: 2025 Tiptap GmbH
|
||||
|
||||
---
|
||||
|
||||
Tsc-alias: A TypeScript compiler plugin for aliasing module paths
|
||||
|
||||
URL: https://github.com/justkey007/tsc-alias
|
||||
License: MIT License
|
||||
Copyright: 2018 Justkey
|
||||
|
||||
---
|
||||
|
||||
Typedoc: A documentation generator for TypeScript projects
|
||||
|
||||
URL: https://typedoc.org/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: Gerrit Birkeland and Contributors
|
||||
|
||||
---
|
||||
|
||||
Typescript: A typed superset of JavaScript that compiles to plain JavaScript
|
||||
|
||||
URL: https://www.typescriptlang.org/
|
||||
License: Apache License, Version 2.0
|
||||
Copyright: 2012-present Microsoft Corporation
|
||||
|
||||
---
|
||||
|
||||
Typescript-eslint: A set of tools for linting TypeScript code
|
||||
|
||||
URL: https://github.com/typescript-eslint/typescript-eslint
|
||||
License: MIT License
|
||||
Copyright: 2019 typescript-eslint and other contributors
|
||||
|
||||
---
|
||||
|
||||
Typescript-json-schema: A library for generating JSON schema from TypeScript types
|
||||
|
||||
URL: https://github.com/YousefED/typescript-json-schema
|
||||
License: BSD 3-Clause License
|
||||
Copyright: 2016 typescript-json-schema contributors
|
||||
|
||||
---
|
||||
|
||||
Umbraco.Code: Provides code-level tools for Umbraco
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco-Code
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco.GitVersioning.Extensions: Utilities for Nerdbank.GitVersioning
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco.GitVersioning.Extensions
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco.JsonSchema.Extensions: Utilities for JSON schema generation
|
||||
|
||||
URL: https://github.com/umbraco/Umbraco.JsonSchema.Extensions
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
Umbraco UI Library: A set of UI components for building web applications
|
||||
|
||||
URL: https://uui.umbraco.com/
|
||||
License: MIT License
|
||||
Copyright: 2005-present Umbraco A/S
|
||||
|
||||
---
|
||||
|
||||
uuid: A library for generating unique identifiers
|
||||
|
||||
URL: https://github.com/uuidjs/uuid
|
||||
License: MIT License
|
||||
Copyright: 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
---
|
||||
|
||||
Vite: A fast build tool and development server for modern web projects
|
||||
|
||||
URL: https://vite.dev/
|
||||
License: MIT License
|
||||
Copyright: 2019-present VoidZero Inc. and Vite contributors
|
||||
|
||||
---
|
||||
|
||||
Vite-plugin-static-copy: A Vite plugin for copying static files
|
||||
|
||||
URL: https://github.com/sapphi-red/vite-plugin-static-copy
|
||||
License: MIT License
|
||||
Copyright: 2021 sapphi-red
|
||||
|
||||
---
|
||||
|
||||
Vite-tsconfig-paths: A Vite plugin for resolving TypeScript paths
|
||||
|
||||
URL: https://github.com/aleclarson/vite-tsconfig-paths
|
||||
License: MIT License
|
||||
Copyright: Alec Larson
|
||||
|
||||
---
|
||||
|
||||
Web Component Analyzer: A tool for analyzing web components
|
||||
|
||||
URL: https://github.com/runem/web-component-analyzer
|
||||
License: MIT License
|
||||
Copyright: 2019 Rune Mehlsen
|
||||
+105
-254
@@ -313,8 +313,8 @@ stages:
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
macOSPart1Of3:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
@@ -325,8 +325,8 @@ stages:
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
macOSPart3Of3:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
@@ -389,8 +389,8 @@ stages:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
@@ -410,8 +410,8 @@ stages:
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure)"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
@@ -432,6 +432,33 @@ stages:
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- powershell: |
|
||||
$maxAttempts = 12
|
||||
$attempt = 0
|
||||
$status = ""
|
||||
|
||||
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
|
||||
Start-Sleep -Seconds 5
|
||||
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
|
||||
$status = docker inspect -f '{{.State.Status}}' mssql
|
||||
|
||||
if ($status -ne 'running') {
|
||||
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
|
||||
$attempt++
|
||||
}
|
||||
}
|
||||
|
||||
if ($status -eq 'running') {
|
||||
Write-Host "SQL Server container is running"
|
||||
docker ps -a
|
||||
} else {
|
||||
Write-Host "SQL Server did not become ready in time. Last known status: $status"
|
||||
docker logs mssql
|
||||
exit 1
|
||||
}
|
||||
displayName: Wait for SQL Server to be ready (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
@@ -495,151 +522,70 @@ stages:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
DatabaseType: SQLite
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
|
||||
LinuxPart2Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTestSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
@@ -647,175 +593,76 @@ stages:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
DatabaseType: SQLServer
|
||||
strategy:
|
||||
matrix:
|
||||
${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }}:
|
||||
LinuxPart1Of3:
|
||||
testCommand: "npm run smokeTest -- --shard=1/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
testFolder: "DefaultConfig"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart2Of3:
|
||||
testCommand: "npm run smokeTest -- --shard=2/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
testFolder: "DefaultConfig"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
LinuxPart3Of3:
|
||||
testCommand: "npm run smokeTest -- --shard=3/3"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
testFolder: "DefaultConfig"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTest -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTest -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run smokeTest -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: eq(variables['Agent.OS'], 'Linux')
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: eq(variables['Agent.OS'], 'Windows_NT')
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish test artifacts
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
###############################################
|
||||
## Release
|
||||
@@ -829,6 +676,8 @@ stages:
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.myGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
|
||||
displayName: Push to pre-release feed
|
||||
steps:
|
||||
- checkout: none
|
||||
@@ -890,6 +739,8 @@ stages:
|
||||
condition: and(succeeded(), or(eq(dependencies.Build.outputs['A.build.NBGV_PublicRelease'], 'True'), ${{parameters.nuGetDeploy}}))
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: "windows-latest" # NuGetCommand@2 is no longer supported on Ubuntu 24.04 so we'll use windows until an alternative is available.
|
||||
displayName: Push to NuGet
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
parameters:
|
||||
- name: testFolder
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: buildConfiguration
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: additionalEnvironmentVariables
|
||||
type: boolean
|
||||
default: False
|
||||
|
||||
steps:
|
||||
- pwsh: |
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Update application to use necessary app settings
|
||||
- pwsh: |
|
||||
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
|
||||
$destinationPath = "UmbracoProject"
|
||||
$jsonFiles = Get-ChildItem -Path $sourcePath -Filter "*.json"
|
||||
if ($jsonFiles) {
|
||||
$jsonFiles | ForEach-Object {
|
||||
Write-Host "Copying: $($_.FullName)"
|
||||
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "No JSON files found."
|
||||
}
|
||||
displayName: Update application to use necessary app settings
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Update application to use necessary App_Plugins
|
||||
- pwsh: |
|
||||
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
|
||||
$destinationPath = "UmbracoProject"
|
||||
$appPluginsFolders = Get-ChildItem -Path $sourcePath -Directory -Filter "App_Plugins"
|
||||
if ($appPluginsFolders) {
|
||||
foreach ($folder in $appPluginsFolders) {
|
||||
Write-Host "Copying folder: $($folder.FullName)"
|
||||
Copy-Item -Path $folder.FullName -Destination $destinationPath -Recurse -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "No App_Plugins found."
|
||||
}
|
||||
displayName: Update application to use necessary app plugins
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Update application to use necessary classes
|
||||
- pwsh: |
|
||||
$sourcePath = "$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/tests/${{ parameters.testFolder }}/AdditionalSetup"
|
||||
$destinationPath = "UmbracoProject"
|
||||
$csharpFiles = Get-ChildItem -Path $sourcePath -Filter "*.cs"
|
||||
if ($csharpFiles) {
|
||||
$csharpFiles | ForEach-Object {
|
||||
Write-Host "Copying: $($_.FullName)"
|
||||
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
|
||||
}
|
||||
} else {
|
||||
Write-Host "No C# files found."
|
||||
}
|
||||
displayName: Update application to use necessary classes
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
@@ -0,0 +1,47 @@
|
||||
parameters:
|
||||
- name: SA_PASSWORD
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: buildConfiguration
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: additionalEnvironmentVariables
|
||||
type: boolean
|
||||
default: False
|
||||
|
||||
- name: DatabaseType
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
steps:
|
||||
# Skips the SQLServer setup if the databaseType does not match
|
||||
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
|
||||
# Start SQL Server Linux
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=${{ parameters.SA_PASSWORD }}" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
# Start SQL Server LocalDB Windows
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# If we want to add additional environment variables to the run step, then we will skip these
|
||||
- ${{ if eq(parameters.additionalEnvironmentVariables, False) }}:
|
||||
# Run application for Linux
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Run application for Windows
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ parameters.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
@@ -0,0 +1,105 @@
|
||||
parameters:
|
||||
- name: ASPNETCORE_URLS
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: testCommand
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: port
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: AZUREB2CTESTUSEREMAIL
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: AZUREB2CTESTUSERPASSWORD
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: DatabaseType
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
steps:
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for either the port of the aspnetcore url
|
||||
- pwsh: |
|
||||
$Port = "${{ parameters.port }}"
|
||||
$Url = "${{ parameters.ASPNETCORE_URLS }}"
|
||||
|
||||
if ($Port -ne "") {
|
||||
Write-Host "Waiting on TCP port $Port"
|
||||
npx wait-on -v --interval 1000 --timeout 120000 "tcp:$Port"
|
||||
} else {
|
||||
Write-Host "Waiting on URL $Url"
|
||||
npx wait-on -v --interval 1000 --timeout 120000 "$Url"
|
||||
}
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install chromium
|
||||
displayName: Install Playwright only with Chromium browser
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: ${{ parameters.testCommand }}
|
||||
displayName: Run Playwright tests
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
AZUREADB2CTESTUSEREMAIL: ${{ parameters.AZUREB2CTESTUSEREMAIL }}
|
||||
AZUREADB2CTESTUSERPASSWORD: ${{ parameters.AZUREB2CTESTUSERPASSWORD }}
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeededOrFailed(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- ${{ if eq(parameters.DatabaseType, 'SQLServer') }}:
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
@@ -0,0 +1,69 @@
|
||||
parameters:
|
||||
- name: nodeVersion
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: PlaywrightUserEmail
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: PlaywrightPassword
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: ASPNETCORE_URLS
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
- name: npm_config_cache
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=${{ parameters.PlaywrightUserEmail }}
|
||||
UMBRACO_USER_PASSWORD=${{ parameters.PlaywrightPassword }}
|
||||
URL=${{ parameters.ASPNETCORE_URLS }}
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: ${{ parameters.npm_config_cache }}
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Install Template
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
displayName: Install Template
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
@@ -3,13 +3,29 @@ name: Nightly_E2E_Test_$(TeamProject)_$(Build.DefinitionName)_$(SourceBranchName
|
||||
pr: none
|
||||
trigger: none
|
||||
|
||||
schedules:
|
||||
- cron: '0 0 * * *'
|
||||
displayName: Daily midnight build
|
||||
branches:
|
||||
include:
|
||||
- v14/dev
|
||||
- v15/dev
|
||||
# schedules:
|
||||
# - cron: '0 0 * * *'
|
||||
# displayName: Daily midnight build
|
||||
# branches:
|
||||
# include:
|
||||
# - v14/dev
|
||||
# - v15/dev
|
||||
|
||||
parameters:
|
||||
- name: differentAppSettingsAcceptanceTests
|
||||
displayName: Run acceptance tests with different app settings
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
- name: skipDefaultConfigAcceptanceTests
|
||||
displayName: Skip tests with DefaultConfig
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
- name: skipIntegrationTests
|
||||
displayName: Skip integration tests
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
variables:
|
||||
nodeVersion: 20
|
||||
@@ -32,25 +48,17 @@ stages:
|
||||
- job: A
|
||||
displayName: Build Umbraco CMS
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
vmImage: "windows-latest"
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 0
|
||||
submodules: true
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 500
|
||||
- template: templates/backoffice-install.yml
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
- template: templates/backoffice-install.yml
|
||||
- script: npm run build:for:cms
|
||||
displayName: Run build (Bellissima)
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
displayName: Run npm ci (Login)
|
||||
workingDirectory: src/Umbraco.Web.UI.Login
|
||||
- script: npm run build
|
||||
displayName: Run npm build (Login)
|
||||
workingDirectory: src/Umbraco.Web.UI.Login
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet restore
|
||||
inputs:
|
||||
@@ -62,7 +70,7 @@ stages:
|
||||
inputs:
|
||||
command: build
|
||||
projects: $(solution)
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg'
|
||||
arguments: "--configuration $(buildConfiguration) --no-restore --property:ContinuousIntegrationBuild=true --property:GeneratePackageOnBuild=true --property:PackageOutputPath=$(Build.ArtifactStagingDirectory)/nupkg"
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish nupkg
|
||||
inputs:
|
||||
@@ -74,8 +82,237 @@ stages:
|
||||
targetPath: $(Build.SourcesDirectory)
|
||||
artifactName: build_output
|
||||
|
||||
- stage: E2E
|
||||
displayName: E2E Tests
|
||||
- job: B
|
||||
displayName: Build Bellissima Package
|
||||
pool:
|
||||
vmImage: "ubuntu-latest"
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 500
|
||||
- template: templates/backoffice-install.yml
|
||||
- script: npm run build:for:npm
|
||||
displayName: Run build:for:npm
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- bash: |
|
||||
echo "##[command]Running npm pack"
|
||||
echo "##[debug]Output directory: $(Build.ArtifactStagingDirectory)"
|
||||
mkdir $(Build.ArtifactStagingDirectory)/npm
|
||||
npm pack --pack-destination $(Build.ArtifactStagingDirectory)/npm
|
||||
mv .npmrc $(Build.ArtifactStagingDirectory)/npm/
|
||||
displayName: Run npm pack
|
||||
workingDirectory: src/Umbraco.Web.UI.Client
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish Bellissima npm artifact
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)/npm
|
||||
artifactName: npm
|
||||
- stage: Integration
|
||||
displayName: Integration Tests
|
||||
dependsOn: Build
|
||||
condition: ${{ eq(parameters.skipIntegrationTests, false) }}
|
||||
jobs:
|
||||
# Integration Tests (SQLite)
|
||||
- job:
|
||||
timeoutInMinutes: 180
|
||||
displayName: Integration Tests (SQLite)
|
||||
strategy:
|
||||
matrix:
|
||||
# Windows:
|
||||
# vmImage: 'windows-latest'
|
||||
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
LinuxPart1Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart2Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace. So this will run all tests that are not part of the Umbraco.Infrastructure namespace
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart4Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
# Filter tests that are part of the ManagementApi namespace
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
macOSPart1Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
macOSPart2Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
macOSPart3Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
macOSPart4Of4:
|
||||
vmImage: "macOS-latest"
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
variables:
|
||||
Tests__Database__DatabaseType: "Sqlite"
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: false
|
||||
lfs: false,
|
||||
fetchDepth: 1
|
||||
fetchFilter: tree:0
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
# Test
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
|
||||
testRunTitle: Integration Tests SQLite - $(Agent.OS)
|
||||
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
|
||||
|
||||
# Integration Tests (SQL Server)
|
||||
- job:
|
||||
timeoutInMinutes: 180
|
||||
displayName: Integration Tests (SQL Server)
|
||||
variables:
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
strategy:
|
||||
matrix:
|
||||
# We split the tests into 4 parts for each OS to reduce the time it takes to run them on the pipeline
|
||||
WindowsPart1Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
WindowsPart2Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
WindowsPart3Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
WindowsPart4Of4:
|
||||
vmImage: "windows-latest"
|
||||
Tests__Database__DatabaseType: LocalDb
|
||||
Tests__Database__SQLServerMasterConnectionString: N/A
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
LinuxPart1Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure namespace but not part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure) & (FullyQualifiedName!~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart2Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the Umbraco.Infrastructure.Service namespace
|
||||
testFilter: "(FullyQualifiedName~Umbraco.Infrastructure.Service)"
|
||||
LinuxPart3Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are not part of the Umbraco.Infrastructure and ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName!~Umbraco.Infrastructure) & (FullyQualifiedName!~ManagementApi)"
|
||||
LinuxPart4Of4:
|
||||
vmImage: "ubuntu-latest"
|
||||
Tests__Database__DatabaseType: SqlServer
|
||||
Tests__Database__SQLServerMasterConnectionString: "Server=(local);User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
# Filter tests that are part of the ManagementApi namespace.
|
||||
testFilter: "(FullyQualifiedName~ManagementApi)"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download build artifacts
|
||||
inputs:
|
||||
artifact: build_output
|
||||
path: $(Build.SourcesDirectory)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- powershell: |
|
||||
$maxAttempts = 12
|
||||
$attempt = 0
|
||||
$status = ""
|
||||
|
||||
while (($status -ne 'running') -and ($attempt -lt $maxAttempts)) {
|
||||
Start-Sleep -Seconds 5
|
||||
# We use the docker inspect command to check the status of the container. If the container is not running, we wait 5 seconds and try again. And if reaches 12 attempts, we fail the build.
|
||||
$status = docker inspect -f '{{.State.Status}}' mssql
|
||||
|
||||
if ($status -ne 'running') {
|
||||
Write-Host "Waiting for SQL Server to be ready... Attempt $($attempt + 1)"
|
||||
$attempt++
|
||||
}
|
||||
}
|
||||
|
||||
if ($status -eq 'running') {
|
||||
Write-Host "SQL Server container is running"
|
||||
docker ps -a
|
||||
} else {
|
||||
Write-Host "SQL Server did not become ready in time. Last known status: $status"
|
||||
docker logs mssql
|
||||
exit 1
|
||||
}
|
||||
displayName: Wait for SQL Server to be ready (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Test
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Run dotnet test
|
||||
inputs:
|
||||
command: test
|
||||
projects: "tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj"
|
||||
testRunTitle: Integration Tests SQL Server - $(Agent.OS)
|
||||
arguments: '--filter "$(testFilter)" --configuration $(buildConfiguration) --no-build'
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
- stage: DefaultConfigE2E
|
||||
displayName: Default Config E2E Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
@@ -103,338 +340,290 @@ stages:
|
||||
- job:
|
||||
displayName: E2E Tests (SQLite)
|
||||
timeoutInMinutes: 180
|
||||
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=Umbraco;Mode=Memory;Cache=Shared;Foreign Keys=True;Pooling=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.Sqlite
|
||||
DatabaseType: SQLite
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
LinuxPart2Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
LinuxPart3Of3:
|
||||
vmImage: "ubuntu-latest"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
WindowsPart1Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
retryCountOnTaskFailure: 3
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Run application
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- job:
|
||||
displayName: E2E Tests (SQL Server)
|
||||
timeoutInMinutes: 180
|
||||
condition: ${{ eq(parameters.skipDefaultConfigAcceptanceTests, false) }}
|
||||
variables:
|
||||
# Connection string
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
DatabaseType: SQLServer
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
strategy:
|
||||
matrix:
|
||||
LinuxPart1Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
LinuxPart2Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
LinuxPart3Of3:
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "ubuntu-latest"
|
||||
SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True"
|
||||
WindowsPart1Of3:
|
||||
testCommand: "npm run test -- --shard=1/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=1/3"
|
||||
WindowsPart2Of3:
|
||||
testCommand: "npm run test -- --shard=2/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=2/3"
|
||||
WindowsPart3Of3:
|
||||
testCommand: "npm run test -- --shard=3/3"
|
||||
testFolder: "DefaultConfig"
|
||||
vmImage: "windows-latest"
|
||||
testCommand: "npm run testSqlite -- --shard=3/3"
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download NuGet artifacts
|
||||
inputs:
|
||||
artifact: nupkg
|
||||
path: $(Agent.BuildDirectory)/app/nupkg
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: Use Node.js $(nodeVersion)
|
||||
inputs:
|
||||
versionSpec: $(nodeVersion)
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: Use .NET SDK from global.json
|
||||
inputs:
|
||||
useGlobalJson: true
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL }}
|
||||
PlaywrightPassword: ${{ variables.UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
- pwsh: |
|
||||
"UMBRACO_USER_LOGIN=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSEREMAIL)
|
||||
UMBRACO_USER_PASSWORD=$(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD)
|
||||
URL=$(ASPNETCORE_URLS)
|
||||
STORAGE_STAGE_PATH=$(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/playwright/.auth/user.json" | Out-File .env
|
||||
displayName: Generate .env
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Cache and restore NPM packages
|
||||
- task: Cache@2
|
||||
displayName: Cache NPM packages
|
||||
inputs:
|
||||
key: 'npm_e2e | "$(Agent.OS)" | $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest/package-lock.json'
|
||||
restoreKeys: |
|
||||
npm_e2e | "$(Agent.OS)"
|
||||
npm_e2e
|
||||
path: $(npm_config_cache)
|
||||
|
||||
- script: npm ci --no-fund --no-audit --prefer-offline
|
||||
workingDirectory: $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest
|
||||
displayName: Restore NPM packages
|
||||
|
||||
# Build application
|
||||
- pwsh: |
|
||||
$cmsVersion = "$(Build.BuildNumber)" -replace "\+",".g"
|
||||
dotnet new nugetconfig
|
||||
dotnet nuget add source ./nupkg --name Local
|
||||
dotnet new install Umbraco.Templates::$cmsVersion
|
||||
dotnet new umbraco --name UmbracoProject --version $cmsVersion --exclude-gitignore --no-restore --no-update-check
|
||||
dotnet restore UmbracoProject
|
||||
cp $(Build.SourcesDirectory)/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/*.cs UmbracoProject
|
||||
dotnet build UmbracoProject --configuration $(buildConfiguration) --no-restore
|
||||
displayName: Restore project
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
condition: succeeded()
|
||||
|
||||
# Start SQL Server
|
||||
- powershell: docker run --name mssql -d -p 1433:1433 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$(SA_PASSWORD)" mcr.microsoft.com/mssql/server:2022-latest
|
||||
displayName: Start SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
- pwsh: SqlLocalDB start MSSQLLocalDB
|
||||
displayName: Start SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
# Run application
|
||||
- stage: AdditionalConfigE2E
|
||||
displayName: Additional Config E2E Tests
|
||||
dependsOn: Build
|
||||
variables:
|
||||
npm_config_cache: $(Pipeline.Workspace)/.npm_e2e
|
||||
ASPNETCORE_URLS: https://localhost:44331
|
||||
PlaywrightPassword: UmbracoAcceptance123!
|
||||
PlaywrightUserEmail: playwright@umbraco.com
|
||||
jobs:
|
||||
- job:
|
||||
displayName: E2E Tests with Different App settings (SQL Server)
|
||||
condition: ${{ eq(parameters.differentAppSettingsAcceptanceTests, true) }}
|
||||
timeoutInMinutes: 180
|
||||
variables:
|
||||
SA_PASSWORD: UmbracoAcceptance123!
|
||||
DatabaseType: SQLServer
|
||||
strategy:
|
||||
matrix:
|
||||
# UnattendedInstallConfig
|
||||
WindowsUnattendedInstallConfig:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "UnattendedInstallConfig"
|
||||
testCommand: "npx playwright test --project=unattendedInstallConfig --grep=InstallSQLServer"
|
||||
port: 44331
|
||||
additionalEnvironmentVariables: false
|
||||
# DeliveryApiConfig
|
||||
WindowsDeliveryApiConfig:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "DeliveryApi"
|
||||
port: ''
|
||||
testCommand: "npx playwright test --project=deliveryApi"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
additionalEnvironmentVariables: false
|
||||
LinuxDeliveryApiConfig:
|
||||
vmImage: "ubuntu-latest"
|
||||
testFolder: "DeliveryApi"
|
||||
port: ''
|
||||
testCommand: "npx playwright test --project=deliveryApi"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);Encrypt=True;TrustServerCertificate=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
additionalEnvironmentVariables: false
|
||||
# ExternalLogin AzureADB2C
|
||||
WindowsExternalLoginAzureADB2C:
|
||||
vmImage: "windows-latest"
|
||||
testFolder: "ExternalLogin\\AzureADB2C"
|
||||
testCommand: "npx playwright test --project=externalLoginAzureADB2C"
|
||||
port: 44331
|
||||
packageName: "Microsoft.AspNetCore.Authentication.OpenIdConnect"
|
||||
packageVersion: "9.0.8"
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True
|
||||
CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient
|
||||
additionalEnvironmentVariables: true
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
steps:
|
||||
# Setup test environment Template
|
||||
- template: nightly-E2E-setup-template.yml
|
||||
parameters:
|
||||
nodeVersion: ${{ variables.nodeVersion }}
|
||||
PlaywrightUserEmail: ${{ variables.PlaywrightUserEmail }}
|
||||
PlaywrightPassword: ${{ variables.PlaywrightPassword }}
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
npm_config_cache: ${{ variables.npm_config_cache }}
|
||||
|
||||
# Install NuGet package if specified in the matrix
|
||||
- pwsh: |
|
||||
Write-Host "Installing package $(packageName) version $(packageVersion)"
|
||||
dotnet add package $(packageName) --version $(packageVersion)
|
||||
displayName: "Install NuGet package: $(packageName)"
|
||||
workingDirectory: $(Agent.BuildDirectory)/app/UmbracoProject
|
||||
condition: and(succeeded(), ne(variables['packageName'], ''), ne(variables['packageVersion'], ''))
|
||||
|
||||
# Build application Template
|
||||
- template: nightly-E2E-build-template.yml
|
||||
parameters:
|
||||
testFolder: $(testFolder)
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true) }}
|
||||
|
||||
# Build application for AzureADB2C
|
||||
- pwsh: |
|
||||
dotnet build UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-restore
|
||||
dotnet dev-certs https
|
||||
displayName: Build application for AzureADB2C
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
env:
|
||||
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
|
||||
AZUREADB2CTENANT: $(AZUREB2CTENANT)
|
||||
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
|
||||
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
|
||||
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
|
||||
condition: and(succeeded(), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
|
||||
|
||||
# Run application Template
|
||||
- template: nightly-E2E-run-application-template.yml
|
||||
parameters:
|
||||
SA_PASSWORD: ${{ variables.SA_PASSWORD }}
|
||||
additionalEnvironmentVariables: ${{ eq(variables['additionalEnvironmentVariables'], true ) }}
|
||||
buildConfiguration: ${{ variables.buildConfiguration }}
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
# Run application for Linux with additional Environment Variables for Azure AD
|
||||
- bash: |
|
||||
nohup dotnet run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
nohup dotnet run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile > $(Build.ArtifactStagingDirectory)/playwright.log 2>&1 &
|
||||
echo "##vso[task.setvariable variable=AcceptanceTestProcessId]$!"
|
||||
displayName: Run application (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
env:
|
||||
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
|
||||
AZUREADB2CTENANT: $(AZUREB2CTENANT)
|
||||
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
|
||||
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
|
||||
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
|
||||
|
||||
# Run application for Windows with additional Environment Variables for Azure AD
|
||||
- pwsh: |
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration $(buildConfiguration) --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
$process = Start-Process dotnet "run --project UmbracoProject --configuration ${{ variables.buildConfiguration }} --no-build --no-launch-profile 2>&1" -PassThru -NoNewWindow -RedirectStandardOutput $(Build.ArtifactStagingDirectory)/playwright.log
|
||||
Write-Host "##vso[task.setvariable variable=AcceptanceTestProcessId]$($process.Id)"
|
||||
displayName: Run application (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'), eq(variables['testFolder'], 'ExternalLogin\AzureADB2C'))
|
||||
workingDirectory: $(Agent.BuildDirectory)/app
|
||||
|
||||
# Ensures we have the package wait-on installed
|
||||
- pwsh: npm install wait-on
|
||||
displayName: Install wait-on package
|
||||
|
||||
# Wait for application to start responding to requests
|
||||
- pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS)
|
||||
displayName: Wait for application
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Install Playwright and dependencies
|
||||
- pwsh: npx playwright install --with-deps
|
||||
displayName: Install Playwright
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
|
||||
# Test
|
||||
- pwsh: $(testCommand)
|
||||
displayName: Run Playwright tests
|
||||
continueOnError: true
|
||||
workingDirectory: tests/Umbraco.Tests.AcceptanceTest
|
||||
env:
|
||||
CI: true
|
||||
CommitId: $(Build.SourceVersion)
|
||||
AgentOs: $(Agent.OS)
|
||||
AZUREADB2CDOMAIN: $(AZUREB2CDOMAIN)
|
||||
AZUREADB2CTENANT: $(AZUREB2CTENANT)
|
||||
AZUREADB2CPOLICY: $(AZUREB2CPOLICY)
|
||||
AZUREADB2CCLIENTID: $(AZUREB2CCLIENTID)
|
||||
AZUREADB2CCLIENTSECRET: $(AZUREB2CCLIENTSECRET)
|
||||
|
||||
# Stop application
|
||||
- bash: kill -15 $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Linux)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: Stop-Process -Id $(AcceptanceTestProcessId)
|
||||
displayName: Stop application (Windows)
|
||||
condition: and(succeeded(), ne(variables.AcceptanceTestProcessId, ''), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Stop SQL Server
|
||||
- pwsh: docker stop mssql
|
||||
displayName: Stop SQL Server Docker image (Linux)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
|
||||
- pwsh: SqlLocalDB stop MSSQLLocalDB
|
||||
displayName: Stop SQL Server LocalDB (Windows)
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT'))
|
||||
|
||||
# Copy artifacts
|
||||
- pwsh: |
|
||||
if (Test-Path tests/Umbraco.Tests.AcceptanceTest/results/*) {
|
||||
Copy-Item tests/Umbraco.Tests.AcceptanceTest/results/* $(Build.ArtifactStagingDirectory) -Recurse
|
||||
}
|
||||
displayName: Copy Playwright results
|
||||
condition: succeededOrFailed()
|
||||
|
||||
# Publish
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: Publish test artifacts
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
artifact: "Acceptance Test Results - $(Agent.JobName) - Attempt #$(System.JobAttempt)"
|
||||
|
||||
# Publish test results
|
||||
- task: PublishTestResults@2
|
||||
displayName: "Publish test results"
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: '*.xml'
|
||||
searchFolder: "tests/Umbraco.Tests.AcceptanceTest/results"
|
||||
testRunTitle: "$(Agent.JobName)"
|
||||
# Run tests Template
|
||||
- template: nightly-E2E-run-tests-template.yml
|
||||
parameters:
|
||||
testCommand: $(testCommand)
|
||||
ASPNETCORE_URLS: ${{ variables.ASPNETCORE_URLS }}
|
||||
port: $(port)
|
||||
AZUREB2CTESTUSEREMAIL: $(AZUREB2CTESTUSEREMAIL)
|
||||
AZUREB2CTESTUSERPASSWORD: $(AZUREB2CTESTUSERPASSWORD)
|
||||
DatabaseType: ${{ variables.DatabaseType }}
|
||||
|
||||
@@ -18,7 +18,12 @@ internal sealed class DeliveryApiOutputCachePolicy : IOutputCachePolicy
|
||||
.RequestServices
|
||||
.GetRequiredService<IRequestPreviewService>();
|
||||
|
||||
context.EnableOutputCaching = requestPreviewService.IsPreview() is false;
|
||||
IApiAccessService apiAccessService = context
|
||||
.HttpContext
|
||||
.RequestServices
|
||||
.GetRequiredService<IApiAccessService>();
|
||||
|
||||
context.EnableOutputCaching = requestPreviewService.IsPreview() is false && apiAccessService.HasPublicAccess();
|
||||
context.ResponseExpirationTimeSpan = _duration;
|
||||
|
||||
return ValueTask.CompletedTask;
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.DocumentBlueprint;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint;
|
||||
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
|
||||
public class ScaffoldDocumentBlueprintController : DocumentBlueprintControllerBase
|
||||
{
|
||||
private readonly IContentBlueprintEditingService _contentBlueprintEditingService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public ScaffoldDocumentBlueprintController(IContentBlueprintEditingService contentBlueprintEditingService, IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_contentBlueprintEditingService = contentBlueprintEditingService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/scaffold")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(DocumentBlueprintResponseModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Scaffold(CancellationToken cancellationToken, Guid id)
|
||||
{
|
||||
IContent? blueprint = await _contentBlueprintEditingService.GetScaffoldedAsync(id);
|
||||
return blueprint is not null
|
||||
? Ok(_umbracoMapper.Map<DocumentBlueprintResponseModel>(blueprint))
|
||||
: DocumentBlueprintNotFound();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.Factories;
|
||||
@@ -5,11 +6,13 @@ using Umbraco.Cms.Api.Management.Routing;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Webhook.Logs;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Logs;
|
||||
|
||||
[VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Webhook}")]
|
||||
[ApiExplorerSettings(GroupName = "Webhook")]
|
||||
[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
|
||||
public class WebhookLogControllerBase : ManagementApiControllerBase
|
||||
{
|
||||
protected PagedViewModel<WebhookLogResponseModel> CreatePagedWebhookLogResponseModel(PagedModel<WebhookLog> logs, IWebhookPresentationFactory webhookPresentationFactory)
|
||||
|
||||
@@ -26,7 +26,7 @@ internal sealed class DocumentVersionPresentationFactory : IDocumentVersionPrese
|
||||
new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
|
||||
.Result),
|
||||
new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
|
||||
new DateTimeOffset(contentVersion.VersionDate, TimeSpan.Zero), // todo align with datetime offset rework
|
||||
new DateTimeOffset(contentVersion.VersionDate),
|
||||
contentVersion.CurrentPublishedVersion,
|
||||
contentVersion.CurrentDraftVersion,
|
||||
contentVersion.PreventCleanup);
|
||||
|
||||
@@ -3485,6 +3485,66 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-blueprint/{id}/scaffold": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Document Blueprint"
|
||||
],
|
||||
"operationId": "GetDocumentBlueprintByIdScaffold",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/DocumentBlueprintResponseModel"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The resource is protected and requires an authentication token"
|
||||
},
|
||||
"403": {
|
||||
"description": "The authenticated user does not have access to this resource"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Backoffice User": [ ]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/umbraco/management/api/v1/document-blueprint/folder": {
|
||||
"post": {
|
||||
"tags": [
|
||||
|
||||
@@ -36,5 +36,7 @@ public class ConfigureUmbracoBackofficeJsonOptions : IConfigureNamedOptions<Json
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonObjectConverter());
|
||||
|
||||
options.JsonSerializerOptions.TypeInfoResolver = _umbracoJsonTypeInfoResolver;
|
||||
|
||||
options.JsonSerializerOptions.MaxDepth = 64; // Ensures the maximum possible value is used, in particular to support handling as best we can levels of nested blocks.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,10 +127,16 @@ internal class EFCoreScope<TDbContext> : CoreScope, IEfCoreScope<TDbContext>
|
||||
|
||||
Locks.ClearLocks(InstanceId);
|
||||
|
||||
if (ParentScope is null)
|
||||
// Since we can nest EFCoreScopes in other scopes derived from CoreScope, we should check whether our ParentScope OR the base ParentScope exists.
|
||||
// Only if neither do do we take responsibility for ensuring the locks are cleared.
|
||||
// Eventually the highest parent will clear the locks.
|
||||
// Further, these locks are a reference to the locks of the highest parent anyway (see the constructor of CoreScope).
|
||||
#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists (justification: provides additional clarify here that this is defined on the base class).
|
||||
if (ParentScope is null && base.HasParentScope is false)
|
||||
{
|
||||
Locks.EnsureLocksCleared(InstanceId);
|
||||
}
|
||||
#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists
|
||||
|
||||
_efCoreScopeProvider.PopAmbientScope();
|
||||
|
||||
|
||||
@@ -26,23 +26,36 @@
|
||||
<ProjectReference Include="..\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Restore and build backoffice project -->
|
||||
<!-- General ignored files -->
|
||||
<ItemGroup>
|
||||
<Content Remove="wwwroot\umbraco\assets\README.md" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<!-- BEGIN: Restore and build backoffice project -->
|
||||
<PropertyGroup>
|
||||
<BackofficeProjectDirectory Condition="'$(BackofficeProjectDirectory)' == ''">..\Umbraco.Web.UI.Client\</BackofficeProjectDirectory>
|
||||
<BackofficeAssetsPath>wwwroot\umbraco\backoffice</BackofficeAssetsPath>
|
||||
<BackofficeAssetsPath>$(ProjectDir)wwwroot\umbraco\backoffice</BackofficeAssetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<BackofficeAssetsInputs Include="$(BackofficeProjectDirectory)package.json;$(BackofficeProjectDirectory)package-lock.json;$(BackofficeProjectDirectory)src\**" Exclude="$(DefaultItemExcludes)" />
|
||||
<Content Remove="$(BackofficeAssetsPath)\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
|
||||
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
|
||||
<Message Text="Skip BuildBackoffice target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||
<Message Text="Skip BuildBackoffice target because '$(BackofficeAssetsPath)' already exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)')" />
|
||||
<Message Text="Call BuildBackoffice target because UmbracoBuild is empty (this is Visual Studio) and '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
|
||||
<CallTarget Targets="BuildBackoffice" Condition="'$(UmbracoBuild)' == '' and !Exists('$(BackofficeAssetsPath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice" BeforeTargets="AssignTargetPaths" Inputs="@(BackofficeAssetsInputs)" Outputs="$(IntermediateOutputPath)backoffice.complete.txt">
|
||||
<Target Name="RestoreBackoffice" Inputs="$(BackofficeProjectDirectory)package-lock.json" Outputs="$(BackofficeProjectDirectory)node_modules\.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Backoffice NPM packages..." />
|
||||
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildBackoffice" DependsOnTargets="RestoreBackoffice">
|
||||
<Message Importance="high" Text="Executing Backoffice NPM build script..." />
|
||||
<Exec Command="npm run build:for:cms" WorkingDirectory="$(BackofficeProjectDirectory)" />
|
||||
<ItemGroup>
|
||||
@@ -61,27 +74,46 @@
|
||||
</DefineStaticWebAssets>
|
||||
</Target>
|
||||
|
||||
<!-- Restore and build login project -->
|
||||
<Target Name="CleanStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
<Message Text="Skip CleanBackoffice target because '$(BackofficeAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(BackofficeAssetsPath)')" />
|
||||
<Message Text="Skip CleanBackoffice target because preserve.backoffice marker file exists" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and Exists('$(SolutionDir)preserve.backoffice')" />
|
||||
<Message Text="Call CleanBackoffice target because '$(BackofficeAssetsPath)' exists and preserve.backoffice marker file doesn't exist" Importance="high" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
|
||||
<CallTarget Targets="CleanBackoffice" Condition="Exists('$(BackofficeAssetsPath)') and !Exists('$(SolutionDir)preserve.backoffice')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanBackoffice">
|
||||
<ItemGroup>
|
||||
<BackofficeDirectories Include="$(BackofficeAssetsPath)" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="@(BackofficeDirectories)" />
|
||||
</Target>
|
||||
<!-- END: Restore and build backoffice project -->
|
||||
|
||||
|
||||
|
||||
<!-- BEGIN: Restore and build login project -->
|
||||
<PropertyGroup>
|
||||
<LoginProjectDirectory Condition="'$(LoginProjectDirectory)' == ''">..\Umbraco.Web.UI.Login\</LoginProjectDirectory>
|
||||
<LoginAssetsPath>wwwroot\umbraco\login</LoginAssetsPath>
|
||||
<LoginAssetsPath>$(ProjectDir)wwwroot\umbraco\login</LoginAssetsPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<LoginAssetsInputs Include="$(LoginProjectDirectory)**" Exclude="$(DefaultItemExcludes)" />
|
||||
<Content Remove="$(LoginAssetsPath)\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="wwwroot\umbraco\assets\README.md" />
|
||||
</ItemGroup>
|
||||
<Target Name="BuildLoginStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
|
||||
<Message Text="Skip BuildLogin target because UmbracoBuild is '$(UmbracoBuild)' (this is not Visual Studio)" Importance="high" Condition="'$(UmbracoBuild)' != ''" />
|
||||
<Message Text="Skip BuildLogin target because '$(LoginAssetsPath)' already exists" Importance="high" Condition="Exists('$(LoginAssetsPath)')" />
|
||||
<Message Text="Call BuildLogin target because UmbracoBuild is empty (this is Visual Studio) and '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
|
||||
<CallTarget Targets="BuildLogin" Condition="'$(UmbracoBuild)' == '' and !Exists('$(LoginAssetsPath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestoreLogin" Inputs="$(LoginProjectDirectory)package-lock.json" Outputs="$(LoginProjectDirectory)node_modules/.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Login NPM packages..." />
|
||||
<Exec Command="npm ci --no-fund --no-audit --prefer-offline" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
<Exec Command="npm i --no-fund --no-audit" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin" BeforeTargets="AssignTargetPaths" Inputs="@(LoginAssetsInputs)" Outputs="$(IntermediateOutputPath)login.complete.txt">
|
||||
<Target Name="BuildLogin" DependsOnTargets="RestoreLogin">
|
||||
<Message Importance="high" Text="Executing Login NPM build script..." />
|
||||
<Exec Command="npm run build" WorkingDirectory="$(LoginProjectDirectory)" />
|
||||
<ItemGroup>
|
||||
@@ -99,4 +131,19 @@
|
||||
<Output TaskParameter="Assets" ItemName="StaticWebAsset" />
|
||||
</DefineStaticWebAssets>
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanLoginStaticAssetsPreconditions" AfterTargets="Clean" Condition="'$(UmbracoBuild)' == ''">
|
||||
<Message Text="Skip CleanLogin target because '$(LoginAssetsPath)' doesn't exist" Importance="high" Condition="!Exists('$(LoginAssetsPath)')" />
|
||||
<Message Text="Skip CleanLogin target because preserve.login marker file exists" Importance="high" Condition="Exists('$(LoginAssetsPath)') and Exists('$(SolutionDir)preserve.login')" />
|
||||
<Message Text="Call CleanLogin target because '$(LoginAssetsPath)' exists and preserve.login marker file doesn't exist" Importance="high" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||
<CallTarget Targets="CleanLogin" Condition="Exists('$(LoginAssetsPath)') and !Exists('$(SolutionDir)preserve.login')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CleanLogin">
|
||||
<ItemGroup>
|
||||
<LoginDirectories Include="$(LoginAssetsPath)" />
|
||||
</ItemGroup>
|
||||
<RemoveDir Directories="@(LoginDirectories)" />
|
||||
</Target>
|
||||
<!-- END: Restore and build login project -->
|
||||
</Project>
|
||||
|
||||
@@ -22,4 +22,6 @@ public static class CacheKeys
|
||||
|
||||
public const string PreviewPropertyCacheKeyPrefix = "Cache.Property.CacheValues[D:";
|
||||
public const string PropertyCacheKeyPrefix = "Cache.Property.CacheValues[P:";
|
||||
|
||||
public const string MemberUserNameCachePrefix = "uRepo_userNameKey+";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -21,9 +23,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
private readonly IDocumentNavigationManagementService _documentNavigationManagementService;
|
||||
private readonly IContentService _contentService;
|
||||
private readonly IDocumentCacheService _documentCacheService;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
private readonly IPublishStatusManagementService _publishStatusManagementService;
|
||||
private readonly IIdKeyMap _idKeyMap;
|
||||
|
||||
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
|
||||
public ContentCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
@@ -38,6 +42,39 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
IContentService contentService,
|
||||
IPublishStatusManagementService publishStatusManagementService,
|
||||
IDocumentCacheService documentCacheService)
|
||||
: this(
|
||||
appCaches,
|
||||
serializer,
|
||||
idKeyMap,
|
||||
domainService,
|
||||
eventAggregator,
|
||||
factory,
|
||||
documentUrlService,
|
||||
domainCacheService,
|
||||
documentNavigationQueryService,
|
||||
documentNavigationManagementService,
|
||||
contentService,
|
||||
publishStatusManagementService,
|
||||
documentCacheService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
|
||||
{
|
||||
}
|
||||
|
||||
public ContentCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IDomainService domainService,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory,
|
||||
IDocumentUrlService documentUrlService,
|
||||
IDomainCacheService domainCacheService,
|
||||
IDocumentNavigationQueryService documentNavigationQueryService,
|
||||
IDocumentNavigationManagementService documentNavigationManagementService,
|
||||
IContentService contentService,
|
||||
IPublishStatusManagementService publishStatusManagementService,
|
||||
IDocumentCacheService documentCacheService,
|
||||
ICacheManager cacheManager)
|
||||
: base(appCaches, serializer, eventAggregator, factory)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
@@ -49,6 +86,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
_contentService = contentService;
|
||||
_documentCacheService = documentCacheService;
|
||||
_publishStatusManagementService = publishStatusManagementService;
|
||||
|
||||
// TODO: Ideally we should inject IElementsCache
|
||||
// this interface is in infrastructure, and changing this is very breaking
|
||||
// so as long as we have the cache manager, which casts the IElementsCache to a simple AppCache we might as well use that.
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
#region Indirect
|
||||
@@ -83,6 +125,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
AppCaches.RuntimeCache.ClearOfType<PublicAccessEntry>();
|
||||
AppCaches.RuntimeCache.ClearByKey(CacheKeys.ContentRecycleBinCacheKey);
|
||||
|
||||
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
|
||||
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
|
||||
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
|
||||
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
|
||||
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
|
||||
_cacheManager.ElementsCache.Clear();
|
||||
|
||||
var idsRemoved = new HashSet<int>();
|
||||
IAppPolicyCache isolatedCache = AppCaches.IsolatedCaches.GetOrCreate<IContent>();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -18,7 +20,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
private readonly IMediaNavigationManagementService _mediaNavigationManagementService;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IMediaCacheService _mediaCacheService;
|
||||
private readonly ICacheManager _cacheManager;
|
||||
|
||||
[Obsolete("Use the constructor with ICacheManager instead, scheduled for removal in V17.")]
|
||||
public MediaCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
@@ -29,6 +33,31 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
IMediaNavigationManagementService mediaNavigationManagementService,
|
||||
IMediaService mediaService,
|
||||
IMediaCacheService mediaCacheService)
|
||||
: this(
|
||||
appCaches,
|
||||
serializer,
|
||||
idKeyMap,
|
||||
eventAggregator,
|
||||
factory,
|
||||
mediaNavigationQueryService,
|
||||
mediaNavigationManagementService,
|
||||
mediaService,
|
||||
mediaCacheService,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ICacheManager>())
|
||||
{
|
||||
}
|
||||
|
||||
public MediaCacheRefresher(
|
||||
AppCaches appCaches,
|
||||
IJsonSerializer serializer,
|
||||
IIdKeyMap idKeyMap,
|
||||
IEventAggregator eventAggregator,
|
||||
ICacheRefresherNotificationFactory factory,
|
||||
IMediaNavigationQueryService mediaNavigationQueryService,
|
||||
IMediaNavigationManagementService mediaNavigationManagementService,
|
||||
IMediaService mediaService,
|
||||
IMediaCacheService mediaCacheService,
|
||||
ICacheManager cacheManager)
|
||||
: base(appCaches, serializer, eventAggregator, factory)
|
||||
{
|
||||
_idKeyMap = idKeyMap;
|
||||
@@ -36,6 +65,9 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
_mediaNavigationManagementService = mediaNavigationManagementService;
|
||||
_mediaService = mediaService;
|
||||
_mediaCacheService = mediaCacheService;
|
||||
|
||||
// TODO: Use IElementsCache instead of ICacheManager, see ContentCacheRefresher for more information.
|
||||
_cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
#region Indirect
|
||||
@@ -87,6 +119,13 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
AppCaches.RuntimeCache.ClearByKey(CacheKeys.MediaRecycleBinCacheKey);
|
||||
Attempt<IAppPolicyCache?> mediaCache = AppCaches.IsolatedCaches.Get<IMedia>();
|
||||
|
||||
// Ideally, we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
|
||||
// The reason for this is that we have no way to know which elements are affected by the changes or what their keys are.
|
||||
// This is because currently published elements live exclusively in a JSON blob in the umbracoPropertyData table.
|
||||
// This means that the only way to resolve these keys is to actually parse this data with a specific value converter, and for all cultures, which is not possible.
|
||||
// If published elements become their own entities with relations, instead of just property data, we can revisit this.
|
||||
_cacheManager.ElementsCache.Clear();
|
||||
|
||||
foreach (JsonPayload payload in payloads)
|
||||
{
|
||||
if (payload.ChangeTypes == TreeChangeTypes.Remove)
|
||||
|
||||
@@ -71,11 +71,22 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase<MemberCache
|
||||
foreach (JsonPayload p in payloads)
|
||||
{
|
||||
_idKeyMap.ClearCache(p.Id);
|
||||
if (memberCache.Success)
|
||||
if (memberCache.Success is false)
|
||||
{
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
|
||||
continue;
|
||||
}
|
||||
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
|
||||
|
||||
// This specific cache key was introduced to fix an issue where the member username could not be the same as the member id, because the cache keys collided.
|
||||
// This is done in a bit of a hacky way, because the cache key is created internally in the repository, but we need to clear it here.
|
||||
// Ideally, we want to use a shared way of generating the key between this and the repository.
|
||||
// Additionally, the RepositoryCacheKeys actually caches the string to avoid re-allocating memory; we would like to also use this in the repository
|
||||
// See:
|
||||
// https://github.com/umbraco/Umbraco-CMS/pull/17350
|
||||
// https://github.com/umbraco/Umbraco-CMS/pull/17815
|
||||
memberCache.Result?.Clear(RepositoryCacheKeys.GetKey<IMember, string>(CacheKeys.MemberUserNameCachePrefix + p.Username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,17 @@ namespace Umbraco.Extensions;
|
||||
public static class ContentSettingsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if file extension is allowed for upload based on (optional) white list and black list
|
||||
/// held in settings.
|
||||
/// Allow upload if extension is whitelisted OR if there is no whitelist and extension is NOT blacklisted.
|
||||
/// Determines if file extension is allowed for upload based on (optional) allow list and deny list held in settings.
|
||||
/// Disallowed file extensions are only considered if there are no allowed file extensions.
|
||||
/// </summary>
|
||||
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension) =>
|
||||
contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) ||
|
||||
(contentSettings.AllowedUploadedFileExtensions.Any() == false &&
|
||||
contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension)) == false);
|
||||
/// <param name="contentSettings">The content settings.</param>
|
||||
/// <param name="extension">The file extension.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the file extension is allowed for upload; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsFileAllowedForUpload(this ContentSettings contentSettings, string extension)
|
||||
=> contentSettings.AllowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) ||
|
||||
(contentSettings.AllowedUploadedFileExtensions.Any() == false && contentSettings.DisallowedUploadedFileExtensions.Any(x => x.InvariantEquals(extension.Trim())) == false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the auto-fill configuration for a specified property alias.
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
<key alias="invalidEmpty">Value cannot be empty</key>
|
||||
<key alias="invalidPattern">Value is invalid, it does not match the correct pattern</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMemberGroupName">Invalid member group name</key>
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
<key alias="unexpectedRange">The value %0% is not expected to contain a range</key>
|
||||
<key alias="invalidRange">The value %0% is not expected to have a to value less than the from value</key>
|
||||
<key alias="entriesShort"><![CDATA[Minimum %0% entries, requires <strong>%1%</strong> more.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="entriesExceed"><![CDATA[Maximum %0% entries, you have entered <strong>%1%</strong> too many.]]></key>
|
||||
<key alias="stringLengthExceeded">The string length exceeds the maximum length of %0% characters, %1% too many.</key>
|
||||
<key alias="entriesAreasMismatch">The content amount requirements are not met for one or more areas.</key>
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
|
||||
@@ -245,13 +245,13 @@ public static class ContentExtensions
|
||||
}
|
||||
|
||||
IEnumerable<ContentSchedule> expires = contentSchedule.GetSchedule(culture, ContentScheduleAction.Expire);
|
||||
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.Now > x.Date))
|
||||
if (expires != null && expires.Any(x => x.Date > DateTime.MinValue && DateTime.UtcNow > x.Date))
|
||||
{
|
||||
return ContentStatus.Expired;
|
||||
}
|
||||
|
||||
IEnumerable<ContentSchedule> release = contentSchedule.GetSchedule(culture, ContentScheduleAction.Release);
|
||||
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.Now))
|
||||
if (release != null && release.Any(x => x.Date > DateTime.MinValue && x.Date > DateTime.UtcNow))
|
||||
{
|
||||
return ContentStatus.AwaitingRelease;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Cms.Core.HostedServices;
|
||||
public interface IBackgroundTaskQueue
|
||||
{
|
||||
/// <summary>
|
||||
/// Enqueue a work item to be executed on in the background.
|
||||
/// Enqueue a work item to be executed in the background.
|
||||
/// </summary>
|
||||
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Umbraco.Cms.Core.IO
|
||||
|
||||
// nothing prevents us to reach the file, security-wise, yet it is outside
|
||||
// this filesystem's root - throw
|
||||
throw new UnauthorizedAccessException($"File original: [{originalPath}] full: [{path}] is outside this filesystem's root.");
|
||||
throw new UnauthorizedAccessException($"Requested path {originalPath} is outside this filesystem's root.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -37,7 +37,7 @@ public class ContentVersionMeta
|
||||
|
||||
public int UserId { get; }
|
||||
|
||||
public DateTime VersionDate { get; }
|
||||
public DateTime VersionDate { get; private set; }
|
||||
|
||||
public bool CurrentPublishedVersion { get; }
|
||||
|
||||
@@ -47,5 +47,7 @@ public class ContentVersionMeta
|
||||
|
||||
public string? Username { get; }
|
||||
|
||||
public void SpecifyVersionDateKind(DateTimeKind kind) => VersionDate = DateTime.SpecifyKind(VersionDate, kind);
|
||||
|
||||
public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
|
||||
@@ -12,14 +15,27 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
|
||||
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
|
||||
|
||||
private readonly ILogger<DataValueReferenceFactoryCollection> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
|
||||
: base(items)
|
||||
: this(
|
||||
items,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<DataValueReferenceFactoryCollection>>())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items, ILogger<DataValueReferenceFactoryCollection> logger)
|
||||
: base(items) => _logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique references from the specified properties.
|
||||
/// </summary>
|
||||
@@ -33,7 +49,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
var references = new HashSet<UmbracoEntityReference>();
|
||||
|
||||
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
|
||||
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
foreach (IGrouping<string, IReadOnlyCollection<IPropertyValue>> propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
{
|
||||
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
|
||||
{
|
||||
@@ -48,7 +64,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
values.Add(propertyValue.PublishedValue);
|
||||
}
|
||||
|
||||
references.UnionWith(GetReferences(dataEditor, values));
|
||||
references.UnionWith(GetReferences(dataEditor, values, propertyValuesByPropertyEditorAlias.Key));
|
||||
}
|
||||
|
||||
return references;
|
||||
@@ -74,14 +90,18 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
/// The references.
|
||||
/// </returns>
|
||||
public ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values) =>
|
||||
GetReferencesEnumerable(dataEditor, values).ToHashSet();
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values)
|
||||
GetReferencesEnumerable(dataEditor, values, null).ToHashSet();
|
||||
|
||||
private ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values, string propertyEditorAlias) =>
|
||||
GetReferencesEnumerable(dataEditor, values, propertyEditorAlias).ToHashSet();
|
||||
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values, string? propertyEditorAlias)
|
||||
{
|
||||
// TODO: We will need to change this once we support tracking via variants/segments
|
||||
// for now, we are tracking values from ALL variants
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
|
||||
{
|
||||
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
|
||||
foreach (UmbracoEntityReference reference in GetReferencesFromPropertyValues(values, dataValueReference, propertyEditorAlias))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
@@ -107,6 +127,38 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesFromPropertyValues(IEnumerable<object?> values, IDataValueReference dataValueReference, string? propertyEditorAlias)
|
||||
{
|
||||
var result = new List<UmbracoEntityReference>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
// When property editors on data types are changed, we could have values that are incompatible with the new editor.
|
||||
// Leading to issues such as:
|
||||
// - https://github.com/umbraco/Umbraco-CMS/issues/17628
|
||||
// - https://github.com/umbraco/Umbraco-CMS/issues/17725
|
||||
// Although some changes like this are not intended to be compatible, we should handle them gracefully and not
|
||||
// error in retrieving references, which would prevent manipulating or deleting the content that uses the data type.
|
||||
try
|
||||
{
|
||||
IEnumerable<UmbracoEntityReference> references = dataValueReference.GetReferences(value);
|
||||
result.AddRange(references);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception but don't throw, continue with the next value.
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error getting references from value {Value} with data editor {DataEditor} and property editor alias {PropertyEditorAlias}.",
|
||||
value,
|
||||
dataValueReference.GetType().FullName,
|
||||
propertyEditorAlias ?? "n/a");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all relation type aliases that are automatically tracked.
|
||||
/// </summary>
|
||||
@@ -117,6 +169,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
[Obsolete("Use GetAllAutomaticRelationTypesAliases. This will be removed in Umbraco 15.")]
|
||||
public ISet<string> GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) =>
|
||||
GetAllAutomaticRelationTypesAliases(propertyEditors);
|
||||
|
||||
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
// Always add default automatic relation types
|
||||
|
||||
@@ -101,7 +101,7 @@ public interface IPublishedCache
|
||||
/// </summary>
|
||||
/// <param name="id">The content type unique identifier.</param>
|
||||
/// <returns>The content type, or null.</returns>
|
||||
[Obsolete("Please use the IContentTypeCacheService instead, scheduled for removal in V16")]
|
||||
[Obsolete("Please use the IPublishedContentTypeCache instead, scheduled for removal in V16")]
|
||||
IPublishedContentType? GetContentType(int id);
|
||||
|
||||
/// <summary>
|
||||
@@ -110,7 +110,7 @@ public interface IPublishedCache
|
||||
/// <param name="alias">The content type alias.</param>
|
||||
/// <returns>The content type, or null.</returns>
|
||||
/// <remarks>The alias is case-insensitive.</remarks>
|
||||
[Obsolete("Please use the IContentTypeCacheService instead, scheduled for removal in V16")]
|
||||
[Obsolete("Please use the IPublishedContentTypeCache instead, scheduled for removal in V16")]
|
||||
IPublishedContentType? GetContentType(string alias);
|
||||
|
||||
/// <summary>
|
||||
@@ -126,6 +126,6 @@ public interface IPublishedCache
|
||||
/// </summary>
|
||||
/// <param name="key">The content type key.</param>
|
||||
/// <returns>The content type, or null.</returns>
|
||||
[Obsolete("Please use the IContentTypeCacheService instead, scheduled for removal in V16")]
|
||||
[Obsolete("Please use the IPublishedContentTypeCache instead, scheduled for removal in V16")]
|
||||
IPublishedContentType? GetContentType(Guid key);
|
||||
}
|
||||
|
||||
@@ -250,6 +250,8 @@ public class CoreScope : ICoreScope
|
||||
_parentScope = coreScope;
|
||||
}
|
||||
|
||||
protected bool HasParentScope => _parentScope is not null;
|
||||
|
||||
protected void HandleScopedNotifications() => _notificationPublisher?.ScopeExit(Completed.HasValue && Completed.Value);
|
||||
|
||||
private void EnsureNotDisposed()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
@@ -35,6 +37,21 @@ internal sealed class ContentBlueprintEditingService
|
||||
return await Task.FromResult(blueprint);
|
||||
}
|
||||
|
||||
public Task<IContent?> GetScaffoldedAsync(Guid key)
|
||||
{
|
||||
IContent? blueprint = ContentService.GetBlueprintById(key);
|
||||
if (blueprint is null)
|
||||
{
|
||||
return Task.FromResult<IContent?>(null);
|
||||
}
|
||||
|
||||
using ICoreScope scope = CoreScopeProvider.CreateCoreScope();
|
||||
scope.Notifications.Publish(new ContentScaffoldedNotification(blueprint, blueprint, Constants.System.Root, new EventMessages()));
|
||||
scope.Complete();
|
||||
|
||||
return Task.FromResult<IContent?>(blueprint);
|
||||
}
|
||||
|
||||
public async Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(Guid contentTypeKey, int skip, int take)
|
||||
{
|
||||
IContentType? contentType = await ContentTypeService.GetAsync(contentTypeKey);
|
||||
|
||||
@@ -44,26 +44,51 @@ public class DocumentUrlService : IDocumentUrlService
|
||||
/// <summary>
|
||||
/// Model used to cache a single published document along with all it's URL segments.
|
||||
/// </summary>
|
||||
private class PublishedDocumentUrlSegments
|
||||
/// <remarks>Internal for the purpose of unit and benchmark testing.</remarks>
|
||||
internal class PublishedDocumentUrlSegments
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the document key.
|
||||
/// </summary>
|
||||
public required Guid DocumentKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language Id.
|
||||
/// </summary>
|
||||
public required int LanguageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the collection of <see cref="UrlSegment"/> for the document, language and state.
|
||||
/// </summary>
|
||||
public required IList<UrlSegment> UrlSegments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the document is a draft version or not.
|
||||
/// </summary>
|
||||
public required bool IsDraft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Model used to represent a URL segment for a document in the cache.
|
||||
/// </summary>
|
||||
public class UrlSegment
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlSegment"/> class.
|
||||
/// </summary>
|
||||
public UrlSegment(string segment, bool isPrimary)
|
||||
{
|
||||
Segment = segment;
|
||||
IsPrimary = isPrimary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL segment string.
|
||||
/// </summary>
|
||||
public string Segment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this URL segment is the primary one for the document, language and state.
|
||||
/// </summary>
|
||||
public bool IsPrimary { get; }
|
||||
}
|
||||
}
|
||||
@@ -168,45 +193,40 @@ public class DocumentUrlService : IDocumentUrlService
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
private static IEnumerable<PublishedDocumentUrlSegments> ConvertToCacheModel(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
|
||||
/// <summary>
|
||||
/// Converts a collection of <see cref="PublishedDocumentUrlSegment"/> to a collection of <see cref="PublishedDocumentUrlSegments"/> for caching purposes.
|
||||
/// </summary>
|
||||
/// <param name="publishedDocumentUrlSegments">The collection of <see cref="PublishedDocumentUrlSegment"/> retrieved from the database on startup.</param>
|
||||
/// <returns>The collection of cache models.</returns>
|
||||
/// <remarks>Internal for the purpose of unit and benchmark testing.</remarks>
|
||||
internal static IEnumerable<PublishedDocumentUrlSegments> ConvertToCacheModel(IEnumerable<PublishedDocumentUrlSegment> publishedDocumentUrlSegments)
|
||||
{
|
||||
var cacheModels = new List<PublishedDocumentUrlSegments>();
|
||||
var cacheModels = new Dictionary<(Guid DocumentKey, int LanguageId, bool IsDraft), PublishedDocumentUrlSegments>();
|
||||
|
||||
foreach (PublishedDocumentUrlSegment model in publishedDocumentUrlSegments)
|
||||
{
|
||||
PublishedDocumentUrlSegments? existingCacheModel = GetModelFromCache(cacheModels, model);
|
||||
if (existingCacheModel is null)
|
||||
(Guid DocumentKey, int LanguageId, bool IsDraft) key = (model.DocumentKey, model.LanguageId, model.IsDraft);
|
||||
|
||||
if (!cacheModels.TryGetValue(key, out PublishedDocumentUrlSegments? existingCacheModel))
|
||||
{
|
||||
cacheModels.Add(new PublishedDocumentUrlSegments
|
||||
cacheModels[key] = new PublishedDocumentUrlSegments
|
||||
{
|
||||
DocumentKey = model.DocumentKey,
|
||||
LanguageId = model.LanguageId,
|
||||
UrlSegments = [new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary)],
|
||||
IsDraft = model.IsDraft,
|
||||
});
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
existingCacheModel.UrlSegments = GetUpdatedUrlSegments(existingCacheModel.UrlSegments, model.UrlSegment, model.IsPrimary);
|
||||
if (existingCacheModel.UrlSegments.Any(x => x.Segment == model.UrlSegment) is false)
|
||||
{
|
||||
existingCacheModel.UrlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(model.UrlSegment, model.IsPrimary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cacheModels;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static PublishedDocumentUrlSegments? GetModelFromCache(List<PublishedDocumentUrlSegments> cacheModels, PublishedDocumentUrlSegment model)
|
||||
=> cacheModels
|
||||
.SingleOrDefault(x => x.DocumentKey == model.DocumentKey && x.LanguageId == model.LanguageId && x.IsDraft == model.IsDraft);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IList<PublishedDocumentUrlSegments.UrlSegment> GetUpdatedUrlSegments(IList<PublishedDocumentUrlSegments.UrlSegment> urlSegments, string segment, bool isPrimary)
|
||||
{
|
||||
if (urlSegments.FirstOrDefault(x => x.Segment == segment) is null)
|
||||
{
|
||||
urlSegments.Add(new PublishedDocumentUrlSegments.UrlSegment(segment, isPrimary));
|
||||
}
|
||||
|
||||
return urlSegments;
|
||||
return cacheModels.Values;
|
||||
}
|
||||
|
||||
private void RemoveFromCache(IScopeContext scopeContext, Guid documentKey, string isoCode, bool isDraft)
|
||||
|
||||
@@ -8,6 +8,8 @@ public interface IContentBlueprintEditingService
|
||||
{
|
||||
Task<IContent?> GetAsync(Guid key);
|
||||
|
||||
Task<IContent?> GetScaffoldedAsync(Guid key) => Task.FromResult<IContent?>(null);
|
||||
|
||||
Task<Attempt<PagedModel<IContent>?, ContentEditingOperationStatus>> GetPagedByContentTypeAsync(
|
||||
Guid contentTypeKey,
|
||||
int skip,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
@@ -88,19 +86,14 @@ internal sealed class PublicAccessService : RepositoryService, IPublicAccessServ
|
||||
{
|
||||
// Get all ids in the path for the content item and ensure they all
|
||||
// parse to ints that are not -1.
|
||||
var ids = contentPath.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => int.TryParse(x, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val) ? val : -1)
|
||||
.Where(x => x != -1)
|
||||
.ToList();
|
||||
|
||||
// start with the deepest id
|
||||
ids.Reverse();
|
||||
// Start with the deepest id.
|
||||
IEnumerable<int> ids = contentPath.GetIdsFromPathReversed().Where(x => x != -1);
|
||||
|
||||
using (ICoreScope scope = ScopeProvider.CreateCoreScope(autoComplete: true))
|
||||
{
|
||||
// This will retrieve from cache!
|
||||
var entries = _publicAccessRepository.GetMany().ToList();
|
||||
foreach (var id in CollectionsMarshal.AsSpan(ids))
|
||||
foreach (var id in ids)
|
||||
{
|
||||
PublicAccessEntry? found = entries.Find(x => x.ProtectedNodeId == id);
|
||||
if (found != null)
|
||||
|
||||
@@ -9,4 +9,13 @@ namespace Umbraco.Cms.Infrastructure.HostedServices;
|
||||
[Obsolete("This has been relocated into Umbraco.Cms.Core. This definition in Umbraco.Cms.Infrastructure is scheduled for removal in Umbraco 17.")]
|
||||
public interface IBackgroundTaskQueue : Core.HostedServices.IBackgroundTaskQueue
|
||||
{
|
||||
/// <summary>
|
||||
/// Enqueue a work item to be executed in the background.
|
||||
/// </summary>
|
||||
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
|
||||
|
||||
/// <summary>
|
||||
/// Dequeue the first item on the queue.
|
||||
/// </summary>
|
||||
Task<Func<CancellationToken, Task>?> DequeueAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ internal class AccessDto
|
||||
[ForeignKey(typeof(NodeDto), Name = "FK_umbracoAccess_umbracoNode_id2")]
|
||||
public int NoAccessNodeId { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
[Column("updateDate")]
|
||||
[Column("updateDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ internal class AccessRuleDto
|
||||
[Column("ruleType")]
|
||||
public string? RuleType { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
[Column("updateDate")]
|
||||
[Column("updateDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ internal class AuditEntryDto
|
||||
[Length(Constants.Audit.IpLength)]
|
||||
public string? PerformingIp { get; set; }
|
||||
|
||||
[Column("eventDateUtc")]
|
||||
[Column("eventDateUtc", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime EventDateUtc { get; set; }
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ConsentDto
|
||||
[Length(512)]
|
||||
public string? Action { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ internal class ContentScheduleDto
|
||||
[NullSetting(NullSetting = NullSettings.Null)] // can be invariant
|
||||
public int? LanguageId { get; set; }
|
||||
|
||||
// NOTE: this date is explicitly stored and treated as UTC despite the lack of "Utc" postfix.
|
||||
[Column("date")]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
|
||||
@@ -27,6 +27,6 @@ internal class ContentVersionCleanupPolicyDto
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? KeepLatestVersionPerDayForDays { get; set; }
|
||||
|
||||
[Column("updated")]
|
||||
[Column("updated", ForceToUtc = false)]
|
||||
public DateTime Updated { get; set; }
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ internal class ContentVersionCultureVariationDto
|
||||
[Column("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[Column("date")] // TODO: db rename to 'updateDate'
|
||||
[Column("date", ForceToUtc = false)] // TODO: db rename to 'updateDate'
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
[Column("availableUserId")] // TODO: db rename to 'updateDate'
|
||||
|
||||
@@ -22,7 +22,7 @@ public class ContentVersionDto
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_NodeId", ForColumns = "nodeId,current", IncludeColumns = "id,versionDate,text,userId,preventCleanup")]
|
||||
public int NodeId { get; set; }
|
||||
|
||||
[Column("versionDate")] // TODO: db rename to 'updateDate'
|
||||
[Column("versionDate", ForceToUtc = false)] // TODO: db rename to 'updateDate'
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime VersionDate { get; set; }
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class CreatedPackageSchemaDto
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public string Value { get; set; } = null!;
|
||||
|
||||
[Column("updateDate")]
|
||||
[Column("updateDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@ internal class DocumentPublishedReadOnlyDto
|
||||
[Column("newest")]
|
||||
public bool Newest { get; set; }
|
||||
|
||||
[Column("updateDate")]
|
||||
[Column("updateDate", ForceToUtc = false)]
|
||||
public DateTime VersionDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ internal class ExternalLoginDto
|
||||
[Index(IndexTypes.NonClustered, ForColumns = "loginProvider,providerKey", Name = "IX_" + TableName + "_ProviderKey")]
|
||||
public string ProviderKey { get; set; } = null!;
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ internal class ExternalLoginTokenDto
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public string Value { get; set; } = null!;
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ internal class KeyValueDto
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string? Value { get; set; }
|
||||
|
||||
[Column("updated")]
|
||||
[Column("updated", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal class LogDto
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public string? EntityType { get; set; }
|
||||
|
||||
[Column("Datestamp")]
|
||||
[Column("Datestamp", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_datestamp", ForColumns = "Datestamp,userId,NodeId")]
|
||||
public DateTime Datestamp { get; set; }
|
||||
|
||||
@@ -45,7 +45,7 @@ internal class MemberDto
|
||||
[Length(255)]
|
||||
public string? SecurityStampToken { get; set; }
|
||||
|
||||
[Column("emailConfirmedDate")]
|
||||
[Column("emailConfirmedDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? EmailConfirmedDate { get; set; }
|
||||
|
||||
@@ -62,15 +62,15 @@ internal class MemberDto
|
||||
[Constraint(Default = 1)]
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
[Column("lastLoginDate")]
|
||||
[Column("lastLoginDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
[Column("lastLockoutDate")]
|
||||
[Column("lastLockoutDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastLockoutDate { get; set; }
|
||||
|
||||
[Column("lastPasswordChangeDate")]
|
||||
[Column("lastPasswordChangeDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastPasswordChangeDate { get; set; }
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class NodeDto
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType", ForColumns = "nodeObjectType,trashed", IncludeColumns = "uniqueId,parentId,level,path,sortOrder,nodeUser,text,createDate")]
|
||||
public Guid? NodeObjectType { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ internal class RelationDto
|
||||
[ForeignKey(typeof(RelationTypeDto))]
|
||||
public int RelationType { get; set; }
|
||||
|
||||
[Column("datetime")]
|
||||
[Column("datetime", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime Datetime { get; set; }
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ internal class ServerRegistrationDto
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_computerName")] // server identity is unique
|
||||
public string? ServerIdentity { get; set; }
|
||||
|
||||
[Column("registeredDate")]
|
||||
[Column("registeredDate", ForceToUtc = false)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime DateRegistered { get; set; }
|
||||
|
||||
[Column("lastNotifiedDate")]
|
||||
[Column("lastNotifiedDate", ForceToUtc = false)]
|
||||
public DateTime DateAccessed { get; set; }
|
||||
|
||||
[Column("isActive")]
|
||||
|
||||
@@ -73,32 +73,32 @@ public class UserDto
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? FailedLoginAttempts { get; set; }
|
||||
|
||||
[Column("lastLockoutDate")]
|
||||
[Column("lastLockoutDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastLockoutDate { get; set; }
|
||||
|
||||
[Column("lastPasswordChangeDate")]
|
||||
[Column("lastPasswordChangeDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastPasswordChangeDate { get; set; }
|
||||
|
||||
[Column("lastLoginDate")]
|
||||
[Column("lastLoginDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
[Column("emailConfirmedDate")]
|
||||
[Column("emailConfirmedDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? EmailConfirmedDate { get; set; }
|
||||
|
||||
[Column("invitedDate")]
|
||||
[Column("invitedDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public DateTime? InvitedDate { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; } = DateTime.Now;
|
||||
|
||||
[Column("updateDate")]
|
||||
[Column("updateDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; } = DateTime.Now;
|
||||
|
||||
@@ -44,12 +44,12 @@ public class UserGroupDto
|
||||
[Obsolete("Is not used anymore Use UserGroup2PermissionDtos instead. This will be removed in Umbraco 18.")]
|
||||
public string? DefaultPermissions { get; set; }
|
||||
|
||||
[Column("createDate")]
|
||||
[Column("createDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
[Column("updateDate")]
|
||||
[Column("updateDate", ForceToUtc = false)]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Constraint(Default = SystemMethods.CurrentDateTime)]
|
||||
public DateTime UpdateDate { get; set; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NPoco;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations;
|
||||
|
||||
@@ -24,7 +24,7 @@ internal class WebhookLogDto
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public string StatusCode { get; set; } = string.Empty;
|
||||
|
||||
[Column(Name = "date")]
|
||||
[Column(Name = "date", ForceToUtc = false)]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + Constants.DatabaseSchema.Tables.WebhookLog + "_date")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
@@ -39,8 +39,11 @@ internal class ContentBaseFactory
|
||||
|
||||
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
|
||||
content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
|
||||
content.Published = dto.Published;
|
||||
content.Edited = dto.Edited;
|
||||
@@ -52,7 +55,7 @@ internal class ContentBaseFactory
|
||||
content.PublishedVersionId = publishedVersionDto.Id;
|
||||
if (dto.Published)
|
||||
{
|
||||
content.PublishDate = publishedVersionDto.ContentVersionDto.VersionDate;
|
||||
content.PublishDate = DateTime.SpecifyKind(publishedVersionDto.ContentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
content.PublishName = publishedVersionDto.ContentVersionDto.Text;
|
||||
content.PublisherId = publishedVersionDto.ContentVersionDto.UserId;
|
||||
}
|
||||
@@ -71,7 +74,7 @@ internal class ContentBaseFactory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an IMedia item from a dto and content type.
|
||||
/// Builds a Media item from a dto and content type.
|
||||
/// </summary>
|
||||
public static Core.Models.Media BuildEntity(ContentDto dto, IMediaType? contentType)
|
||||
{
|
||||
@@ -97,8 +100,8 @@ internal class ContentBaseFactory
|
||||
|
||||
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
|
||||
content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
@@ -111,7 +114,7 @@ internal class ContentBaseFactory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds an IMedia item from a dto and content type.
|
||||
/// Builds a Member item from a dto and member type.
|
||||
/// </summary>
|
||||
public static Member BuildEntity(MemberDto dto, IMemberType? contentType)
|
||||
{
|
||||
@@ -126,7 +129,9 @@ internal class ContentBaseFactory
|
||||
|
||||
content.Id = dto.NodeId;
|
||||
content.SecurityStamp = dto.SecurityStampToken;
|
||||
content.EmailConfirmedDate = dto.EmailConfirmedDate;
|
||||
content.EmailConfirmedDate = dto.EmailConfirmedDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.EmailConfirmedDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
content.PasswordConfiguration = dto.PasswordConfig;
|
||||
content.Key = nodeDto.UniqueId;
|
||||
content.VersionId = contentVersionDto.Id;
|
||||
@@ -140,14 +145,20 @@ internal class ContentBaseFactory
|
||||
|
||||
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
|
||||
content.CreateDate = nodeDto.CreateDate;
|
||||
content.UpdateDate = contentVersionDto.VersionDate;
|
||||
content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
|
||||
content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
|
||||
content.FailedPasswordAttempts = dto.FailedPasswordAttempts ?? default;
|
||||
content.IsLockedOut = dto.IsLockedOut;
|
||||
content.IsApproved = dto.IsApproved;
|
||||
content.LastLoginDate = dto.LastLoginDate;
|
||||
content.LastLockoutDate = dto.LastLockoutDate;
|
||||
content.LastPasswordChangeDate = dto.LastPasswordChangeDate;
|
||||
content.LastLockoutDate = dto.LastLockoutDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLockoutDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
content.LastLoginDate = dto.LastLoginDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLoginDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
content.LastPasswordChangeDate = dto.LastPasswordChangeDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastPasswordChangeDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
content.ResetDirtyProperties(false);
|
||||
@@ -186,7 +197,7 @@ internal class ContentBaseFactory
|
||||
new ContentScheduleDto
|
||||
{
|
||||
Action = x.Action.ToString(),
|
||||
Date = x.Date,
|
||||
Date = DateTime.SpecifyKind(x.Date, DateTimeKind.Local),
|
||||
NodeId = entity.Id,
|
||||
LanguageId = languageRepository.GetIdByIsoCode(x.Culture, false),
|
||||
Id = x.Id,
|
||||
@@ -261,7 +272,7 @@ internal class ContentBaseFactory
|
||||
UserId = entity.CreatorId,
|
||||
Text = entity.Name,
|
||||
NodeObjectType = objectType,
|
||||
CreateDate = entity.CreateDate,
|
||||
CreateDate = DateTime.SpecifyKind(entity.CreateDate, DateTimeKind.Local),
|
||||
};
|
||||
|
||||
return dto;
|
||||
@@ -275,7 +286,7 @@ internal class ContentBaseFactory
|
||||
{
|
||||
Id = entity.VersionId,
|
||||
NodeId = entity.Id,
|
||||
VersionDate = entity.UpdateDate,
|
||||
VersionDate = DateTime.SpecifyKind(entity.UpdateDate, DateTimeKind.Local),
|
||||
UserId = entity.WriterId,
|
||||
Current = true, // always building the current one
|
||||
Text = entity.Name,
|
||||
|
||||
@@ -39,16 +39,25 @@ internal static class UserFactory
|
||||
user.Language = dto.UserLanguage;
|
||||
user.SecurityStamp = dto.SecurityStampToken;
|
||||
user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0;
|
||||
user.LastLockoutDate = dto.LastLockoutDate;
|
||||
user.LastLoginDate = dto.LastLoginDate;
|
||||
user.LastPasswordChangeDate = dto.LastPasswordChangeDate;
|
||||
user.CreateDate = dto.CreateDate;
|
||||
user.UpdateDate = dto.UpdateDate;
|
||||
user.Avatar = dto.Avatar;
|
||||
user.EmailConfirmedDate = dto.EmailConfirmedDate;
|
||||
user.InvitedDate = dto.InvitedDate;
|
||||
user.Kind = (UserKind)dto.Kind;
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
user.LastLockoutDate = dto.LastLockoutDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLockoutDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
user.LastLoginDate = dto.LastLoginDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastLoginDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
user.LastPasswordChangeDate = dto.LastPasswordChangeDate.HasValue
|
||||
? DateTime.SpecifyKind(dto.LastPasswordChangeDate.Value, DateTimeKind.Local)
|
||||
: null;
|
||||
user.CreateDate = DateTime.SpecifyKind(dto.CreateDate, DateTimeKind.Local);
|
||||
user.UpdateDate = DateTime.SpecifyKind(dto.UpdateDate, DateTimeKind.Local);
|
||||
|
||||
// reset dirty initial properties (U4-1946)
|
||||
user.ResetDirtyProperties(false);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
|
||||
List<LogDto>? dtos = Database.Fetch<LogDto>(sql);
|
||||
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList();
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, DateTime.SpecifyKind(x.Datestamp, DateTimeKind.Local))).ToList();
|
||||
}
|
||||
|
||||
public void CleanLogs(int maximumAgeOfLogsInMinutes)
|
||||
@@ -104,12 +104,12 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
totalRecords = page.TotalItems;
|
||||
|
||||
var items = page.Items.Select(
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp)).ToList();
|
||||
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, DateTime.SpecifyKind(dto.Datestamp, DateTimeKind.Local))).ToList();
|
||||
|
||||
// map the DateStamp
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
items[i].CreateDate = page.Items[i].Datestamp;
|
||||
items[i].CreateDate = DateTime.SpecifyKind(page.Items[i].Datestamp, DateTimeKind.Local);
|
||||
}
|
||||
|
||||
return items;
|
||||
@@ -149,7 +149,7 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
LogDto? dto = Database.First<LogDto>(sql);
|
||||
return dto == null
|
||||
? null
|
||||
: new AuditItem(dto.NodeId, Enum<AuditType>.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp);
|
||||
: new AuditItem(dto.NodeId, Enum<AuditType>.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, DateTime.SpecifyKind(dto.Datestamp, DateTimeKind.Local));
|
||||
}
|
||||
|
||||
protected override IEnumerable<IAuditItem> PerformGetAll(params int[]? ids) => throw new NotImplementedException();
|
||||
@@ -162,7 +162,7 @@ internal class AuditRepository : EntityRepositoryBase<int, IAuditItem>, IAuditRe
|
||||
|
||||
List<LogDto>? dtos = Database.Fetch<LogDto>(sql);
|
||||
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList();
|
||||
return dtos.Select(x => new AuditItem(x.NodeId, Enum<AuditType>.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, DateTime.SpecifyKind(x.Datestamp, DateTimeKind.Local))).ToList();
|
||||
}
|
||||
|
||||
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
|
||||
|
||||
@@ -400,15 +400,17 @@ public class DocumentRepository : ContentRepositoryBase<int, IContent, DocumentR
|
||||
{
|
||||
foreach (ContentVariation v in contentVariation)
|
||||
{
|
||||
content.SetCultureInfo(v.Culture, v.Name, v.Date);
|
||||
content.SetCultureInfo(v.Culture, v.Name, DateTime.SpecifyKind(v.Date, DateTimeKind.Local));
|
||||
}
|
||||
}
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
if (content.PublishedState is PublishedState.Published && content.PublishedVersionId > 0 && contentVariations.TryGetValue(content.PublishedVersionId, out contentVariation))
|
||||
{
|
||||
foreach (ContentVariation v in contentVariation)
|
||||
{
|
||||
content.SetPublishInfo(v.Culture, v.Name, v.Date);
|
||||
content.SetPublishInfo(v.Culture, v.Name, DateTime.SpecifyKind(v.Date, DateTimeKind.Local));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -1,3 +1,4 @@
|
||||
using System.Data;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -98,6 +99,16 @@ internal class DocumentVersionRepository : IDocumentVersionRepository
|
||||
Page<ContentVersionMeta>? page =
|
||||
_scopeAccessor.AmbientScope?.Database.Page<ContentVersionMeta>(pageIndex + 1, pageSize, query);
|
||||
|
||||
// Dates stored in the database are local server time, but for SQL Server, will be considered
|
||||
// as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
|
||||
if (page is not null)
|
||||
{
|
||||
foreach (ContentVersionMeta item in page.Items)
|
||||
{
|
||||
item.SpecifyVersionDateKind(DateTimeKind.Local);
|
||||
}
|
||||
}
|
||||
|
||||
totalRecords = page?.TotalItems ?? 0;
|
||||
|
||||
return page?.Items;
|
||||
|
||||
@@ -39,7 +39,6 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
|
||||
private readonly ITagRepository _tagRepository;
|
||||
private bool _passwordConfigInitialized;
|
||||
private string? _passwordConfigJson;
|
||||
private const string UsernameCacheKey = "uRepo_userNameKey+";
|
||||
|
||||
public MemberRepository(
|
||||
IScopeAccessor scopeAccessor,
|
||||
@@ -327,7 +326,7 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
|
||||
}
|
||||
|
||||
public IMember? GetByUsername(string? username) =>
|
||||
_memberByUsernameCachePolicy.GetByUserName(UsernameCacheKey, username, PerformGetByUsername, PerformGetAllByUsername);
|
||||
_memberByUsernameCachePolicy.GetByUserName(CacheKeys.MemberUserNameCachePrefix, username, PerformGetByUsername, PerformGetAllByUsername);
|
||||
|
||||
public int[] GetMemberIds(string[] usernames)
|
||||
{
|
||||
@@ -609,7 +608,7 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
|
||||
|
||||
protected override void PersistDeletedItem(IMember entity)
|
||||
{
|
||||
_memberByUsernameCachePolicy.DeleteByUserName(UsernameCacheKey, entity.Username);
|
||||
_memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username);
|
||||
base.PersistDeletedItem(entity);
|
||||
}
|
||||
|
||||
@@ -943,7 +942,7 @@ public class MemberRepository : ContentRepositoryBase<int, IMember, MemberReposi
|
||||
|
||||
OnUowRefreshedEntity(new MemberRefreshNotification(entity, new EventMessages()));
|
||||
|
||||
_memberByUsernameCachePolicy.DeleteByUserName(UsernameCacheKey, entity.Username);
|
||||
_memberByUsernameCachePolicy.DeleteByUserName(CacheKeys.MemberUserNameCachePrefix, entity.Username);
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -98,17 +99,17 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
|
||||
continue;
|
||||
}
|
||||
|
||||
var districtValues = valuesByPropertyEditorAlias.Distinct().ToArray();
|
||||
var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();
|
||||
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference reference)
|
||||
{
|
||||
foreach (UmbracoEntityReference value in districtValues.SelectMany(reference.GetReferences))
|
||||
foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
|
||||
{
|
||||
result.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, districtValues);
|
||||
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);
|
||||
|
||||
foreach (UmbracoEntityReference value in references)
|
||||
{
|
||||
@@ -291,6 +292,11 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
|
||||
bool canUpdateInvariantData,
|
||||
HashSet<string> allowedCultures)
|
||||
{
|
||||
if (canUpdateInvariantData is false && targetValue is null)
|
||||
{
|
||||
return sourceValue;
|
||||
}
|
||||
|
||||
BlockEditorData<TValue, TLayout>? source = BlockEditorValues.DeserializeAndClean(sourceValue);
|
||||
BlockEditorData<TValue, TLayout>? target = BlockEditorValues.DeserializeAndClean(targetValue);
|
||||
|
||||
@@ -310,31 +316,29 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
|
||||
bool canUpdateInvariantData,
|
||||
HashSet<string> allowedCultures)
|
||||
{
|
||||
source = UpdateSourceInvariantData(source, target, canUpdateInvariantData);
|
||||
var mergedInvariant = UpdateSourceInvariantData(source, target, canUpdateInvariantData);
|
||||
|
||||
if (source is null && target is null)
|
||||
// if the structure (invariant) is not defined after merger, the target content does not matter
|
||||
if (mergedInvariant is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (source is null && target?.Layout is not null)
|
||||
// since we merged the invariant data (layout) before we get to this point
|
||||
// we just need an empty valid object to run comparisons at this point
|
||||
if (source is null)
|
||||
{
|
||||
source = new BlockEditorData<TValue, TLayout>([], CreateWithLayout(target.Layout));
|
||||
}
|
||||
else if (target is null && source?.Layout is not null)
|
||||
{
|
||||
target = new BlockEditorData<TValue, TLayout>([], CreateWithLayout(source.Layout));
|
||||
source = new BlockEditorData<TValue, TLayout>([], new TValue());
|
||||
}
|
||||
|
||||
// at this point the layout should have been merged or fallback created
|
||||
if (source is null || target is null)
|
||||
{
|
||||
throw new ArgumentException("invalid sourceValue or targetValue");
|
||||
}
|
||||
// update the target with the merged invariant
|
||||
target!.BlockValue.Layout = mergedInvariant.BlockValue.Layout;
|
||||
|
||||
// remove all the blocks that are no longer part of the layout
|
||||
target.BlockValue.ContentData.RemoveAll(contentBlock =>
|
||||
target.Layout!.Any(layoutItem => layoutItem.ReferencesContent(contentBlock.Key)) is false);
|
||||
// remove any exposes that no longer have content assigned to them
|
||||
target.BlockValue.Expose.RemoveAll(expose => target.BlockValue.ContentData.Any(data => data.Key == expose.ContentKey) is false);
|
||||
|
||||
target.BlockValue.SettingsData.RemoveAll(settingsBlock =>
|
||||
target.Layout!.Any(layoutItem => layoutItem.ReferencesSetting(settingsBlock.Key)) is false);
|
||||
@@ -342,16 +346,80 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
|
||||
CleanupVariantValues(source.BlockValue.ContentData, target.BlockValue.ContentData, canUpdateInvariantData, allowedCultures);
|
||||
CleanupVariantValues(source.BlockValue.SettingsData, target.BlockValue.SettingsData, canUpdateInvariantData, allowedCultures);
|
||||
|
||||
// every source block value for a culture that is not allowed to be edited should be present on the target
|
||||
RestoreMissingValues(
|
||||
source.BlockValue.ContentData,
|
||||
target.BlockValue.ContentData,
|
||||
mergedInvariant.Layout!,
|
||||
(layoutItem, itemData) => layoutItem.ContentKey == itemData.Key,
|
||||
canUpdateInvariantData,
|
||||
allowedCultures);
|
||||
RestoreMissingValues(
|
||||
source.BlockValue.SettingsData,
|
||||
target.BlockValue.SettingsData,
|
||||
mergedInvariant.Layout!,
|
||||
(layoutItem, itemData) => layoutItem.SettingsKey == itemData.Key,
|
||||
canUpdateInvariantData,
|
||||
allowedCultures);
|
||||
|
||||
// update the expose list from source for any blocks that were restored
|
||||
var missingSourceExposes =
|
||||
source.BlockValue.Expose.Where(sourceExpose =>
|
||||
target.BlockValue.Expose.Any(targetExpose => targetExpose.ContentKey == sourceExpose.ContentKey) is false
|
||||
&& target.BlockValue.ContentData.Any(data => data.Key == sourceExpose.ContentKey)).ToList();
|
||||
foreach (BlockItemVariation missingSourceExpose in missingSourceExposes)
|
||||
{
|
||||
target.BlockValue.Expose.Add(missingSourceExpose);
|
||||
}
|
||||
|
||||
return target.BlockValue;
|
||||
}
|
||||
|
||||
private void RestoreMissingValues(
|
||||
List<BlockItemData> sourceBlockItemData,
|
||||
List<BlockItemData> targetBlockItemData,
|
||||
IEnumerable<TLayout> mergedLayout,
|
||||
Func<TLayout, BlockItemData, bool> relevantBlockItemMatcher,
|
||||
bool canUpdateInvariantData,
|
||||
HashSet<string> allowedCultures)
|
||||
{
|
||||
IEnumerable<BlockItemData> blockItemsToCheck = sourceBlockItemData.Where(itemData =>
|
||||
mergedLayout.Any(layoutItem => relevantBlockItemMatcher(layoutItem, itemData)));
|
||||
foreach (BlockItemData blockItemData in blockItemsToCheck)
|
||||
{
|
||||
var relevantValues = blockItemData.Values.Where(value =>
|
||||
(value.Culture is null && canUpdateInvariantData is false)
|
||||
|| (value.Culture is not null && allowedCultures.Contains(value.Culture) is false)).ToList();
|
||||
if (relevantValues.Count < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockItemData targetBlockData =
|
||||
targetBlockItemData.FirstOrDefault(itemData => itemData.Key == blockItemData.Key)
|
||||
?? new BlockItemData(blockItemData.Key, blockItemData.ContentTypeKey, blockItemData.ContentTypeAlias);
|
||||
foreach (BlockPropertyValue missingValue in relevantValues.Where(value => targetBlockData.Values.Any(targetValue =>
|
||||
targetValue.Alias == value.Alias
|
||||
&& targetValue.Culture == value.Culture
|
||||
&& targetValue.Segment == value.Segment) is false))
|
||||
{
|
||||
targetBlockData.Values.Add(missingValue);
|
||||
}
|
||||
|
||||
if (targetBlockItemData.Any(existingBlockItemData => existingBlockItemData.Key == targetBlockData.Key) is false)
|
||||
{
|
||||
targetBlockItemData.Add(blockItemData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupVariantValues(
|
||||
List<BlockItemData> sourceBlockItems,
|
||||
List<BlockItemData> targetBlockItems,
|
||||
bool canUpdateInvariantData,
|
||||
HashSet<string> allowedCultures)
|
||||
{
|
||||
// merge the source values into the target values for culture
|
||||
// merge the source values into the target values per culture
|
||||
foreach (BlockItemData targetBlockItem in targetBlockItems)
|
||||
{
|
||||
BlockItemData? sourceBlockItem = sourceBlockItems.FirstOrDefault(i => i.Key == targetBlockItem.Key);
|
||||
|
||||
+2
-27
@@ -25,65 +25,40 @@ internal sealed class CacheRefreshingNotificationHandler :
|
||||
{
|
||||
private readonly IDocumentCacheService _documentCacheService;
|
||||
private readonly IMediaCacheService _mediaCacheService;
|
||||
private readonly IElementsCache _elementsCache;
|
||||
private readonly IRelationService _relationService;
|
||||
private readonly IPublishedContentTypeCache _publishedContentTypeCache;
|
||||
|
||||
public CacheRefreshingNotificationHandler(
|
||||
IDocumentCacheService documentCacheService,
|
||||
IMediaCacheService mediaCacheService,
|
||||
IElementsCache elementsCache,
|
||||
IRelationService relationService,
|
||||
IPublishedContentTypeCache publishedContentTypeCache)
|
||||
{
|
||||
_documentCacheService = documentCacheService;
|
||||
_mediaCacheService = mediaCacheService;
|
||||
_elementsCache = elementsCache;
|
||||
_relationService = relationService;
|
||||
_publishedContentTypeCache = publishedContentTypeCache;
|
||||
}
|
||||
|
||||
public async Task HandleAsync(ContentRefreshNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
ClearElementsCache();
|
||||
|
||||
await _documentCacheService.RefreshContentAsync(notification.Entity);
|
||||
}
|
||||
=> await _documentCacheService.RefreshContentAsync(notification.Entity);
|
||||
|
||||
public async Task HandleAsync(ContentDeletedNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (IContent deletedEntity in notification.DeletedEntities)
|
||||
{
|
||||
ClearElementsCache();
|
||||
await _documentCacheService.DeleteItemAsync(deletedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleAsync(MediaRefreshNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
ClearElementsCache();
|
||||
await _mediaCacheService.RefreshMediaAsync(notification.Entity);
|
||||
}
|
||||
=> await _mediaCacheService.RefreshMediaAsync(notification.Entity);
|
||||
|
||||
public async Task HandleAsync(MediaDeletedNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (IMedia deletedEntity in notification.DeletedEntities)
|
||||
{
|
||||
ClearElementsCache();
|
||||
await _mediaCacheService.DeleteItemAsync(deletedEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearElementsCache()
|
||||
{
|
||||
// Ideally we'd like to not have to clear the entire cache here. However, this was the existing behavior in NuCache.
|
||||
// The reason for this is that we have no way to know which elements are affected by the changes. or what their keys are.
|
||||
// This is because currently published elements lives exclusively in a JSON blob in the umbracoPropertyData table.
|
||||
// This means that the only way to resolve these keys are to actually parse this data with a specific value converter, and for all cultures, which is not feasible.
|
||||
// If published elements become their own entities with relations, instead of just property data, we can revisit this,
|
||||
_elementsCache.Clear();
|
||||
}
|
||||
|
||||
public Task HandleAsync(ContentTypeRefreshedNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
const ContentTypeChangeTypes types // only for those that have been refreshed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
@@ -115,12 +115,10 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
// When unpublishing a node, a payload with RefreshBranch is published, so we don't have to worry about this.
|
||||
// Similarly, when a branch is published, next time the content is requested, the parent will be published,
|
||||
// this works because we don't cache null values.
|
||||
if (preview is false && contentCacheNode is not null)
|
||||
if (preview is false && contentCacheNode is not null && HasPublishedAncestorPath(contentCacheNode.Key) is false)
|
||||
{
|
||||
if (HasPublishedAncestorPath(contentCacheNode.Key) is false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// Careful not to early return here. We need to complete the scope even if returning null.
|
||||
contentCacheNode = null;
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
+145
-145
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "15.4.0-rc",
|
||||
"version": "15.5.0-rc",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"version": "15.4.0-rc",
|
||||
"version": "15.5.0-rc",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"./src/packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@tiptap/core": "2.11.5",
|
||||
"@tiptap/extension-character-count": "^2.11.5",
|
||||
"@tiptap/extension-image": "2.11.5",
|
||||
"@tiptap/extension-link": "2.11.5",
|
||||
"@tiptap/extension-placeholder": "2.11.5",
|
||||
"@tiptap/extension-subscript": "2.11.5",
|
||||
"@tiptap/extension-superscript": "2.11.5",
|
||||
"@tiptap/extension-table": "2.11.5",
|
||||
"@tiptap/extension-table-cell": "2.11.5",
|
||||
"@tiptap/extension-table-header": "2.11.5",
|
||||
"@tiptap/extension-table-row": "2.11.5",
|
||||
"@tiptap/extension-text-align": "2.11.5",
|
||||
"@tiptap/extension-underline": "2.11.5",
|
||||
"@tiptap/pm": "2.11.5",
|
||||
"@tiptap/starter-kit": "2.11.5",
|
||||
"@tiptap/core": "2.11.7",
|
||||
"@tiptap/extension-character-count": "2.11.7",
|
||||
"@tiptap/extension-image": "2.11.7",
|
||||
"@tiptap/extension-link": "2.11.7",
|
||||
"@tiptap/extension-placeholder": "2.11.7",
|
||||
"@tiptap/extension-subscript": "2.11.7",
|
||||
"@tiptap/extension-superscript": "2.11.7",
|
||||
"@tiptap/extension-table": "2.11.7",
|
||||
"@tiptap/extension-table-cell": "2.11.7",
|
||||
"@tiptap/extension-table-header": "2.11.7",
|
||||
"@tiptap/extension-table-row": "2.11.7",
|
||||
"@tiptap/extension-text-align": "2.11.7",
|
||||
"@tiptap/extension-underline": "2.11.7",
|
||||
"@tiptap/pm": "2.11.7",
|
||||
"@tiptap/starter-kit": "2.11.7",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@umbraco-ui/uui": "^1.13.0",
|
||||
"@umbraco-ui/uui-css": "^1.13.0",
|
||||
@@ -94,7 +94,7 @@
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"typescript-json-schema": "^0.65.1",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-static-copy": "^2.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"web-component-analyzer": "^2.0.0"
|
||||
@@ -2534,9 +2534,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.5.tgz",
|
||||
"integrity": "sha512-jb0KTdUJaJY53JaN7ooY3XAxHQNoMYti/H6ANo707PsLXVeEqJ9o8+eBup1JU5CuwzrgnDc2dECt2WIGX9f8Jw==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.7.tgz",
|
||||
"integrity": "sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2547,9 +2547,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-blockquote": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.5.tgz",
|
||||
"integrity": "sha512-MZfcRIzKRD8/J1hkt/eYv49060GTL6qGR3NY/oTDuw2wYzbQXXLEbjk8hxAtjwNn7G+pWQv3L+PKFzZDxibLuA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.7.tgz",
|
||||
"integrity": "sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2560,9 +2560,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bold": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.5.tgz",
|
||||
"integrity": "sha512-OAq03MHEbl7MtYCUzGuwb0VpOPnM0k5ekMbEaRILFU5ZC7cEAQ36XmPIw1dQayrcuE8GZL35BKub2qtRxyC9iA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.7.tgz",
|
||||
"integrity": "sha512-VTR3JlldBixXbjpLTFme/Bxf1xeUgZZY3LTlt5JDlCW3CxO7k05CIa+kEZ8LXpog5annytZDUVtWqxrNjmsuHQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2573,9 +2573,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bullet-list": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.5.tgz",
|
||||
"integrity": "sha512-VXwHlX6A/T6FAspnyjbKDO0TQ+oetXuat6RY1/JxbXphH42nLuBaGWJ6pgy6xMl6XY8/9oPkTNrfJw/8/eeRwA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.7.tgz",
|
||||
"integrity": "sha512-WbPogE2/Q3e3/QYgbT1Sj4KQUfGAJNc5pvb7GrUbvRQsAh7HhtuO8hqdDwH8dEdD/cNUehgt17TO7u8qV6qeBw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2586,9 +2586,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-character-count": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.11.5.tgz",
|
||||
"integrity": "sha512-Da2VGb7ClmKwXdQdQC2735qylYD8/MQAPA0skPEcHxcDTDuI8ibyIDnMPnczgS/hR5g0TYE2DQp/dkhJXeovkQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.11.7.tgz",
|
||||
"integrity": "sha512-gcVbKou+uxzg8N0BBKceLwtpWvN8g2TIjTuCdyAcAPukX63DqVWOkofFHn1RqZbstJmtF4pTGZs9OH/GJrp27Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2600,9 +2600,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.5.tgz",
|
||||
"integrity": "sha512-xOvHevNIQIcCCVn9tpvXa1wBp0wHN/2umbAZGTVzS+AQtM7BTo0tz8IyzwxkcZJaImONcUVYLOLzt2AgW1LltA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.7.tgz",
|
||||
"integrity": "sha512-VpPO1Uy/eF4hYOpohS/yMOcE1C07xmMj0/D989D9aS1x95jWwUVrSkwC+PlWMUBx9PbY2NRsg1ZDwVvlNKZ6yQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2613,9 +2613,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code-block": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.5.tgz",
|
||||
"integrity": "sha512-ksxMMvqLDlC+ftcQLynqZMdlJT1iHYZorXsXw/n+wuRd7YElkRkd6YWUX/Pq/njFY6lDjKiqFLEXBJB8nrzzBA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.7.tgz",
|
||||
"integrity": "sha512-To/y/2H04VWqiANy53aXjV7S6fA86c2759RsH1hTIe57jA1KyE7I5tlAofljOLZK/covkGmPeBddSPHGJbz++Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2627,9 +2627,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-document": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.5.tgz",
|
||||
"integrity": "sha512-7I4BRTpIux2a0O2qS3BDmyZ5LGp3pszKbix32CmeVh7lN9dV7W5reDqtJJ9FCZEEF+pZ6e1/DQA362dflwZw2g==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.7.tgz",
|
||||
"integrity": "sha512-95ouJXPjdAm9+VBRgFo4lhDoMcHovyl/awORDI8gyEn0Rdglt+ZRZYoySFzbVzer9h0cre+QdIwr9AIzFFbfdA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2640,9 +2640,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-dropcursor": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.5.tgz",
|
||||
"integrity": "sha512-uIN7L3FU0904ec7FFFbndO7RQE/yiON4VzAMhNn587LFMyWO8US139HXIL4O8dpZeYwYL3d1FnDTflZl6CwLlg==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.7.tgz",
|
||||
"integrity": "sha512-63mL+nxQILizsr5NbmgDeOjFEWi34BLt7evwL6UUZEVM15K8V1G8pD9Y0kCXrZYpHWz0tqFRXdrhDz0Ppu8oVw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2654,9 +2654,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-gapcursor": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.5.tgz",
|
||||
"integrity": "sha512-kcWa+Xq9cb6lBdiICvLReuDtz/rLjFKHWpW3jTTF3FiP3wx4H8Rs6bzVtty7uOVTfwupxZRiKICAMEU6iT0xrQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.7.tgz",
|
||||
"integrity": "sha512-EceesmPG7FyjXZ8EgeJPUov9G1mAf2AwdypxBNH275g6xd5dmU/KvjoFZjmQ0X1ve7mS+wNupVlGxAEUYoveew==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2668,9 +2668,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-hard-break": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.5.tgz",
|
||||
"integrity": "sha512-q9doeN+Yg9F5QNTG8pZGYfNye3tmntOwch683v0CCVCI4ldKaLZ0jG3NbBTq+mosHYdgOH2rNbIORlRRsQ+iYQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.7.tgz",
|
||||
"integrity": "sha512-zTkZSA6q+F5sLOdCkiC2+RqJQN0zdsJqvFIOVFL/IDVOnq6PZO5THzwRRLvOSnJJl3edRQCl/hUgS0L5sTInGQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2681,9 +2681,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-heading": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.5.tgz",
|
||||
"integrity": "sha512-x/MV53psJ9baRcZ4k4WjnCUBMt8zCX7mPlKVT+9C/o+DEs/j/qxPLs95nHeQv70chZpSwCQCt93xMmuF0kPoAg==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.7.tgz",
|
||||
"integrity": "sha512-8kWh7y4Rd2fwxfWOhFFWncHdkDkMC1Z60yzIZWjIu72+6yQxvo8w3yeb7LI7jER4kffbMmadgcfhCHC/fkObBA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2694,9 +2694,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-history": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.5.tgz",
|
||||
"integrity": "sha512-b+wOS33Dz1azw6F1i9LFTEIJ/gUui0Jwz5ZvmVDpL2ZHBhq1Ui0/spTT+tuZOXq7Y/uCbKL8Liu4WoedIvhboQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.7.tgz",
|
||||
"integrity": "sha512-Cu5x3aS13I040QSRoLdd+w09G4OCVfU+azpUqxufZxeNs9BIJC+0jowPLeOxKDh6D5GGT2A8sQtxc6a/ssbs8g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2708,9 +2708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-horizontal-rule": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.5.tgz",
|
||||
"integrity": "sha512-3up2r1Du8/5/4ZYzTC0DjTwhgPI3dn8jhOCLu73m5F3OGvK/9whcXoeWoX103hYMnGDxBlfOje71yQuN35FL4A==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.7.tgz",
|
||||
"integrity": "sha512-uVmQwD2dzZ5xwmvUlciy0ItxOdOfQjH6VLmu80zyJf8Yu7mvwP8JyxoXUX0vd1xHpwAhgQ9/ozjIWYGIw79DPQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2722,9 +2722,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-image": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.11.5.tgz",
|
||||
"integrity": "sha512-HbUq9AL8gb8eSuQfY/QKkvMc66ZFN/b6jvQAILGArNOgalUfGizoC6baKTJShaExMSPjBZlaAHtJiQKPaGRHaA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.11.7.tgz",
|
||||
"integrity": "sha512-YvCmTDB7Oo+A56tR4S/gcNaYpqU4DDlSQcRp5IQvmQV5EekSe0lnEazGDoqOCwsit9qQhj4MPQJhKrnaWrJUrg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2735,9 +2735,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-italic": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.5.tgz",
|
||||
"integrity": "sha512-9VGfb2/LfPhQ6TjzDwuYLRvw0A6VGbaIp3F+5Mql8XVdTBHb2+rhELbyhNGiGVR78CaB/EiKb6dO9xu/tBWSYA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.7.tgz",
|
||||
"integrity": "sha512-r985bkQfG0HMpmCU0X0p/Xe7U1qgRm2mxvcp6iPCuts2FqxaCoyfNZ8YnMsgVK1mRhM7+CQ5SEg2NOmQNtHvPw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2748,9 +2748,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-link": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.5.tgz",
|
||||
"integrity": "sha512-4Iu/aPzevbYpe50xDI0ZkqRa6nkZ9eF270Ue2qaF3Ab47nehj+9Jl78XXzo8+LTyFMnrETI73TAs1aC/IGySeQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.7.tgz",
|
||||
"integrity": "sha512-qKIowE73aAUrnQCIifYP34xXOHOsZw46cT/LBDlb0T60knVfQoKVE4ku08fJzAV+s6zqgsaaZ4HVOXkQYLoW7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"linkifyjs": "^4.2.0"
|
||||
@@ -2765,9 +2765,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list-item": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.5.tgz",
|
||||
"integrity": "sha512-Mp5RD/pbkfW1vdc6xMVxXYcta73FOwLmblQlFNn/l/E5/X1DUSA4iGhgDDH4EWO3swbs03x2f7Zka/Xoj3+WLg==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.7.tgz",
|
||||
"integrity": "sha512-6ikh7Y+qAbkSuIHXPIINqfzmWs5uIGrylihdZ9adaIyvrN1KSnWIqrZIk/NcZTg5YFIJlXrnGSRSjb/QM3WUhw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2778,9 +2778,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-ordered-list": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.5.tgz",
|
||||
"integrity": "sha512-Cu8KwruBNWAaEfshRQR0yOSaUKAeEwxW7UgbvF9cN/zZuKgK5uZosPCPTehIFCcRe+TBpRtZQh+06f/gNYpYYg==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.7.tgz",
|
||||
"integrity": "sha512-bLGCHDMB0vbJk7uu8bRg8vES3GsvxkX7Cgjgm/6xysHFbK98y0asDtNxkW1VvuRreNGz4tyB6vkcVCfrxl4jKw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2791,9 +2791,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-paragraph": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.5.tgz",
|
||||
"integrity": "sha512-YFBWeg7xu/sBnsDIF/+nh9Arf7R0h07VZMd0id5Ydd2Qe3c1uIZwXxeINVtH0SZozuPIQFAT8ICe9M0RxmE+TA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.7.tgz",
|
||||
"integrity": "sha512-Pl3B4q6DJqTvvAdraqZaNP9Hh0UWEHL5nNdxhaRNuhKaUo7lq8wbDSIxIW3lvV0lyCs0NfyunkUvSm1CXb6d4Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2804,9 +2804,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-placeholder": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.5.tgz",
|
||||
"integrity": "sha512-Pr+0Ju/l2ZvXMd9VQxtaoSZbs0BBp1jbBDqwms88ctpyvQFRfLSfSkqudQcSHyw2ROOz2E31p/7I7fpI8Y0CLA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.7.tgz",
|
||||
"integrity": "sha512-/06zXV4HIjYoiaUq1fVJo/RcU8pHbzx21evOpeG/foCfNpMI4xLU/vnxdUi6/SQqpZMY0eFutDqod1InkSOqsg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2818,9 +2818,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-strike": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.5.tgz",
|
||||
"integrity": "sha512-PVfUiCqrjvsLpbIoVlegSY8RlkR64F1Rr2RYmiybQfGbg+AkSZXDeO0eIrc03//4gua7D9DfIozHmAKv1KN3ow==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.7.tgz",
|
||||
"integrity": "sha512-D6GYiW9F24bvAY7XMOARNZbC8YGPzdzWdXd8VOOJABhf4ynMi/oW4NNiko+kZ67jn3EGaKoz32VMJzNQgYi1HA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2831,9 +2831,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-subscript": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.11.5.tgz",
|
||||
"integrity": "sha512-VpaSzxku/Bcvf4SgDB2K5d0E+FNA/56iJHMygg/WXsq2F4tMMUEivQHI/n+17ndUEO4Wybz0wItnM1G2JfRuLQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.11.7.tgz",
|
||||
"integrity": "sha512-I25ZexCddFJ9701DCCtQbX3Vtxzj5d9ss2GAXVweIUCdATCScaebsznyUQoN5papmhTxXsw5OD+K2ZHxP82pew==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2844,9 +2844,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-superscript": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.11.5.tgz",
|
||||
"integrity": "sha512-sK6v2G0zFfGW+j9CmYp2e+tyZ3FTa3dP0xY4kJzefgZcHhMJLlLnjxBRwHCSi/jj5ie6WdZT4KoEooxnPs1Vzw==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.11.7.tgz",
|
||||
"integrity": "sha512-dNRpCcRJs0Qvv0sZRgbH7Y5hDVbWsGSZjtwFCs/mysPrvHqmXjzo7568kYWTggxEYxnXw6n0FfkCAEHlt0N90Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2857,9 +2857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-table": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.11.5.tgz",
|
||||
"integrity": "sha512-NKXLhKWdAdURklm98YkCd2ai4fh8jY8HS/+X2s/2QiQt8Z98CU1keCm35fJEEExM234iB/hCqG5vY4JgTc0Tvw==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.11.7.tgz",
|
||||
"integrity": "sha512-rfwWkNXz/EZuhc8lylsCWPbx0Xr5FlIhreWFyeoXYrDEO3x4ytYcVOpNmbabJYP2semfM0PvPR5o84zfFkLZyg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2871,9 +2871,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-table-cell": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.11.5.tgz",
|
||||
"integrity": "sha512-S967Au0pgeULstP3FaasOf/LEh72p61Ooh1PcUMF/az4x8EeGgpcEUARpVUxsGxLFvogv6LmhPHZdtcGgdHcBw==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.11.7.tgz",
|
||||
"integrity": "sha512-JMOkSYRckc5SJP86yGGiHzCxCR8ecrRENvTWAKib6qer2tutxs5u42W+Z8uTcHC2dRz7Fv54snOkDoqPwkf6cw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2884,9 +2884,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-table-header": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.11.5.tgz",
|
||||
"integrity": "sha512-O1iBtzZP1XZDi4h1Xmgq1T63il+fpKPvBIMZ0JJH9TyCw5i5rcrMLL2dyy5zaWK3BFRJuYBNSke4c+VWnr/g6w==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.11.7.tgz",
|
||||
"integrity": "sha512-wPRKpliS5QQXgsp//ZjXrHMdLICMkjg2fUrQinOiBa7wDL5C7Y+SehtuK4s2tjeAkyAdj+nepfftyBRIlUSMXg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2897,9 +2897,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-table-row": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.11.5.tgz",
|
||||
"integrity": "sha512-+/VWhCuW24BcM5aaIc/f0bC6ZR1Q5gnuqw13MIo7gyPx7iIY6BXK8roGiZSs8wYAN4uBEf3EKFm0bSZwQuAeyg==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.11.7.tgz",
|
||||
"integrity": "sha512-K254RiXWGXGjz5Cm835hqfQiwnYXm8aw6oOa3isDh4A1B+1Ev4DB2vEDKMrgaOor3nbTsSYmAx2iEMrZSbpaRg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2910,9 +2910,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.5.tgz",
|
||||
"integrity": "sha512-Gq1WwyhFpCbEDrLPIHt5A8aLSlf8bfz4jm417c8F/JyU0J5dtYdmx0RAxjnLw1i7ZHE7LRyqqAoS0sl7JHDNSQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.7.tgz",
|
||||
"integrity": "sha512-wObCn8qZkIFnXTLvBP+X8KgaEvTap/FJ/i4hBMfHBCKPGDx99KiJU6VIbDXG8d5ZcFZE0tOetK1pP5oI7qgMlQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2923,9 +2923,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text-align": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.11.5.tgz",
|
||||
"integrity": "sha512-Ei0zDpH5N9EV59ogydK4HTKa4lCPicCsQllM5n/Nf2tUJPir3aiYxzJ73FzhComD4Hpo1ANYnmssBhy8QeoPZA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.11.7.tgz",
|
||||
"integrity": "sha512-3M8zd9ROADXazVNpgR6Ejs1evSvBveN36qN4GgV71GqrNlTcjqYgQcXFLQrsd2hnE+aXir8/8bLJ+aaJXDninA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2936,9 +2936,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text-style": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.5.tgz",
|
||||
"integrity": "sha512-YUmYl0gILSd/u/ZkOmNxjNXVw+mu8fpC2f8G4I4tLODm0zCx09j9DDEJXSrM5XX72nxJQqtSQsCpNKnL0hfeEQ==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.7.tgz",
|
||||
"integrity": "sha512-LHO6DBg/9SkCQFdWlVfw9nolUmw+Cid94WkTY+7IwrpyG2+ZGQxnKpCJCKyeaFNbDoYAtvu0vuTsSXeCkgShcA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2949,9 +2949,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-underline": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.5.tgz",
|
||||
"integrity": "sha512-YpWHXNIkSoRSuzT2cvgKpyJ2tTz3LzqkTM64uC+uTJ8cUkvXIWUWejJR42q8ma/mTlQe4lHff4IQ0Sf58Digtw==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.7.tgz",
|
||||
"integrity": "sha512-NtoQw6PGijOAtXC6G+0Aq0/Z5wwEjPhNHs8nsjXogfWIgaj/aI4/zfBnA06eI3WT+emMYQTl0fTc4CUPnLVU8g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2962,9 +2962,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/pm": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.5.tgz",
|
||||
"integrity": "sha512-z9JFtqc5ZOsdQLd9vRnXfTCQ8v5ADAfRt9Nm7SqP6FUHII8E1hs38ACzf5xursmth/VonJYb5+73Pqxk1hGIPw==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.7.tgz",
|
||||
"integrity": "sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-changeset": "^2.2.1",
|
||||
@@ -2981,7 +2981,7 @@
|
||||
"prosemirror-schema-basic": "^1.2.3",
|
||||
"prosemirror-schema-list": "^1.4.1",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-tables": "^1.6.3",
|
||||
"prosemirror-tables": "^1.6.4",
|
||||
"prosemirror-trailing-node": "^3.0.0",
|
||||
"prosemirror-transform": "^1.10.2",
|
||||
"prosemirror-view": "^1.37.0"
|
||||
@@ -2992,32 +2992,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/starter-kit": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.5.tgz",
|
||||
"integrity": "sha512-SLI7Aj2ruU1t//6Mk8f+fqW+18uTqpdfLUJYgwu0CkqBckrkRZYZh6GVLk/02k3H2ki7QkFxiFbZrdbZdng0JA==",
|
||||
"version": "2.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.7.tgz",
|
||||
"integrity": "sha512-K+q51KwNU/l0kqRuV5e1824yOLVftj6kGplGQLvJG56P7Rb2dPbM/JeaDbxQhnHT/KDGamG0s0Po0M3pPY163A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^2.11.5",
|
||||
"@tiptap/extension-blockquote": "^2.11.5",
|
||||
"@tiptap/extension-bold": "^2.11.5",
|
||||
"@tiptap/extension-bullet-list": "^2.11.5",
|
||||
"@tiptap/extension-code": "^2.11.5",
|
||||
"@tiptap/extension-code-block": "^2.11.5",
|
||||
"@tiptap/extension-document": "^2.11.5",
|
||||
"@tiptap/extension-dropcursor": "^2.11.5",
|
||||
"@tiptap/extension-gapcursor": "^2.11.5",
|
||||
"@tiptap/extension-hard-break": "^2.11.5",
|
||||
"@tiptap/extension-heading": "^2.11.5",
|
||||
"@tiptap/extension-history": "^2.11.5",
|
||||
"@tiptap/extension-horizontal-rule": "^2.11.5",
|
||||
"@tiptap/extension-italic": "^2.11.5",
|
||||
"@tiptap/extension-list-item": "^2.11.5",
|
||||
"@tiptap/extension-ordered-list": "^2.11.5",
|
||||
"@tiptap/extension-paragraph": "^2.11.5",
|
||||
"@tiptap/extension-strike": "^2.11.5",
|
||||
"@tiptap/extension-text": "^2.11.5",
|
||||
"@tiptap/extension-text-style": "^2.11.5",
|
||||
"@tiptap/pm": "^2.11.5"
|
||||
"@tiptap/core": "^2.11.7",
|
||||
"@tiptap/extension-blockquote": "^2.11.7",
|
||||
"@tiptap/extension-bold": "^2.11.7",
|
||||
"@tiptap/extension-bullet-list": "^2.11.7",
|
||||
"@tiptap/extension-code": "^2.11.7",
|
||||
"@tiptap/extension-code-block": "^2.11.7",
|
||||
"@tiptap/extension-document": "^2.11.7",
|
||||
"@tiptap/extension-dropcursor": "^2.11.7",
|
||||
"@tiptap/extension-gapcursor": "^2.11.7",
|
||||
"@tiptap/extension-hard-break": "^2.11.7",
|
||||
"@tiptap/extension-heading": "^2.11.7",
|
||||
"@tiptap/extension-history": "^2.11.7",
|
||||
"@tiptap/extension-horizontal-rule": "^2.11.7",
|
||||
"@tiptap/extension-italic": "^2.11.7",
|
||||
"@tiptap/extension-list-item": "^2.11.7",
|
||||
"@tiptap/extension-ordered-list": "^2.11.7",
|
||||
"@tiptap/extension-paragraph": "^2.11.7",
|
||||
"@tiptap/extension-strike": "^2.11.7",
|
||||
"@tiptap/extension-text": "^2.11.7",
|
||||
"@tiptap/extension-text-style": "^2.11.7",
|
||||
"@tiptap/pm": "^2.11.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -16885,9 +16885,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
|
||||
"integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@umbraco-cms/backoffice",
|
||||
"license": "MIT",
|
||||
"version": "15.4.0-rc",
|
||||
"version": "15.5.0-rc",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": null,
|
||||
@@ -202,21 +202,21 @@
|
||||
"npm": ">=10.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/core": "2.11.5",
|
||||
"@tiptap/extension-character-count": "^2.11.5",
|
||||
"@tiptap/extension-image": "2.11.5",
|
||||
"@tiptap/extension-link": "2.11.5",
|
||||
"@tiptap/extension-placeholder": "2.11.5",
|
||||
"@tiptap/extension-subscript": "2.11.5",
|
||||
"@tiptap/extension-superscript": "2.11.5",
|
||||
"@tiptap/extension-table": "2.11.5",
|
||||
"@tiptap/extension-table-cell": "2.11.5",
|
||||
"@tiptap/extension-table-header": "2.11.5",
|
||||
"@tiptap/extension-table-row": "2.11.5",
|
||||
"@tiptap/extension-text-align": "2.11.5",
|
||||
"@tiptap/extension-underline": "2.11.5",
|
||||
"@tiptap/pm": "2.11.5",
|
||||
"@tiptap/starter-kit": "2.11.5",
|
||||
"@tiptap/core": "2.11.7",
|
||||
"@tiptap/extension-character-count": "2.11.7",
|
||||
"@tiptap/extension-image": "2.11.7",
|
||||
"@tiptap/extension-link": "2.11.7",
|
||||
"@tiptap/extension-placeholder": "2.11.7",
|
||||
"@tiptap/extension-subscript": "2.11.7",
|
||||
"@tiptap/extension-superscript": "2.11.7",
|
||||
"@tiptap/extension-table": "2.11.7",
|
||||
"@tiptap/extension-table-cell": "2.11.7",
|
||||
"@tiptap/extension-table-header": "2.11.7",
|
||||
"@tiptap/extension-table-row": "2.11.7",
|
||||
"@tiptap/extension-text-align": "2.11.7",
|
||||
"@tiptap/extension-underline": "2.11.7",
|
||||
"@tiptap/pm": "2.11.7",
|
||||
"@tiptap/starter-kit": "2.11.7",
|
||||
"@types/diff": "^7.0.1",
|
||||
"@umbraco-ui/uui": "^1.13.0",
|
||||
"@umbraco-ui/uui-css": "^1.13.0",
|
||||
@@ -284,7 +284,7 @@
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"typescript-json-schema": "^0.65.1",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-static-copy": "^2.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"web-component-analyzer": "^2.0.0"
|
||||
|
||||
@@ -2097,7 +2097,7 @@ export default {
|
||||
duplicateUsername: "Username '%0%' is already taken",
|
||||
customValidation: 'Custom validation',
|
||||
entriesShort: 'Minimum %0% entries, requires <strong>%1%</strong> more.',
|
||||
entriesExceed: 'Maximum %0% entries, <strong>%1%</strong> too many.',
|
||||
entriesExceed: 'Maximum %0% entries, you have entered <strong>%1%</strong> too many.',
|
||||
entriesAreasMismatch: 'The content amount requirements are not met for one or more areas.',
|
||||
},
|
||||
healthcheck: {
|
||||
|
||||
@@ -2168,7 +2168,7 @@ export default {
|
||||
invalidPattern: 'Value is invalid, it does not match the correct pattern',
|
||||
customValidation: 'Custom validation',
|
||||
entriesShort: 'Minimum %0% entries, requires <strong>%1%</strong> more.',
|
||||
entriesExceed: 'Maximum %0% entries, <strong>%1%</strong> too many.',
|
||||
entriesExceed: 'Maximum %0% entries, you have entered <strong>%1%</strong> too many.',
|
||||
entriesAreasMismatch: 'The content amount requirements are not met for one or more areas.',
|
||||
invalidMemberGroupName: 'Invalid member group name',
|
||||
invalidUserGroupName: 'Invalid user group name',
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3523,6 +3523,12 @@ export type PutDocumentBlueprintByIdMoveData = {
|
||||
|
||||
export type PutDocumentBlueprintByIdMoveResponse = (string);
|
||||
|
||||
export type GetDocumentBlueprintByIdScaffoldData = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type GetDocumentBlueprintByIdScaffoldResponse = ((DocumentBlueprintResponseModel));
|
||||
|
||||
export type PostDocumentBlueprintFolderData = {
|
||||
requestBody?: (CreateFolderRequestModel);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* This Source Code has been derived from Jameson Little's base64-js.
|
||||
* This Source Code has been derived from base64-js.
|
||||
* https://github.com/beatgammit/base64-js
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Copyright © 2014 Jameson Little.
|
||||
* Modifications are licensed under the MIT License.
|
||||
* Copyright © 2014 Jameson Little
|
||||
* Copyright © 2024 Umbraco A/S
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
const lookup: string[] = [];
|
||||
|
||||
+5
-2
@@ -1,9 +1,12 @@
|
||||
This Source Code has been derived from router-slot.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2018 Andreas Mehlsen andmehlsen@gmail.com
|
||||
Copyright © 2018 Andreas Mehlsen <andmehlsen@gmail.com>
|
||||
Copyright © 2023 Umbraco A/S
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
/**
|
||||
* Header apps are displayed in the top right corner of the backoffice
|
||||
* The two provided header apps are the search and the user menu
|
||||
*/
|
||||
export interface ManifestHeaderApp extends ManifestElement {
|
||||
export interface ManifestHeaderApp extends ManifestElement, ManifestWithDynamicConditions<UmbExtensionConditionConfig> {
|
||||
type: 'headerApp';
|
||||
//meta: MetaHeaderApp;
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
Lucide License
|
||||
ISC License <https://lucide.dev/license>
|
||||
|
||||
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
Simple Icons
|
||||
CC0 1.0 Universal license <https://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
|
||||
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
|
||||
@@ -1,8 +1,10 @@
|
||||
/* This Source Code has been derived from Lee Kelleher's Contentment.
|
||||
/*
|
||||
* This Source Code has been derived from Contentment.
|
||||
* https://github.com/leekelleher/umbraco-contentment/blob/develop/src/Umbraco.Community.Contentment/DataEditors/Bytes/bytes.js
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
* Copyright © 2019 Lee Kelleher.
|
||||
* Modifications are licensed under the MIT License.
|
||||
* Copyright © 2016-2023 Lee Kelleher
|
||||
* Copyright © 2024 Umbraco A/S
|
||||
* Originally licensed under the Mozilla Public License, v. 2.0
|
||||
* Relicensed under the MIT License with permission from the copyright holder.
|
||||
*/
|
||||
|
||||
export interface IFormatBytesOptions {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './validation.context.js';
|
||||
export * from './validation.context-token.js';
|
||||
export * from './validation-messages.manager.js';
|
||||
export * from './server-model-validator.context-token.js';
|
||||
export * from './server-model-validator.context.js';
|
||||
|
||||
+14
-1
@@ -4,10 +4,23 @@ import { UMB_DOCUMENT_BLUEPRINT_DETAIL_STORE_CONTEXT } from './document-blueprin
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
export class UmbDocumentBlueprintDetailRepository extends UmbDetailRepositoryBase<UmbDocumentBlueprintDetailModel> {
|
||||
export class UmbDocumentBlueprintDetailRepository extends UmbDetailRepositoryBase<
|
||||
UmbDocumentBlueprintDetailModel,
|
||||
UmbDocumentBlueprintServerDataSource
|
||||
> {
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UmbDocumentBlueprintServerDataSource, UMB_DOCUMENT_BLUEPRINT_DETAIL_STORE_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an existing document blueprint by its unique identifier for scaffolding purposes, i.e. to create a new document based on an existing blueprint.
|
||||
* @param {string} unique - The unique identifier of the document blueprint.
|
||||
* @returns {UmbRepositoryResponse<UmbDocumentBlueprintDetailModel>} - The document blueprint data.
|
||||
* @memberof UmbDocumentBlueprintDetailRepository
|
||||
*/
|
||||
scaffoldByUnique(unique: string) {
|
||||
return this.detailDataSource.scaffoldByUnique(unique);
|
||||
}
|
||||
}
|
||||
|
||||
export { UmbDocumentBlueprintDetailRepository as api };
|
||||
|
||||
+52
-31
@@ -1,9 +1,10 @@
|
||||
import type { UmbDocumentBlueprintDetailModel } from '../../types.js';
|
||||
import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../../entity.js';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbDataSourceResponse, UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
|
||||
import type {
|
||||
CreateDocumentBlueprintRequestModel,
|
||||
DocumentBlueprintResponseModel,
|
||||
UpdateDocumentBlueprintRequestModel,
|
||||
} from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { DocumentBlueprintService } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
@@ -74,7 +75,7 @@ export class UmbDocumentBlueprintServerDataSource implements UmbDetailDataSource
|
||||
* @returns {*}
|
||||
* @memberof UmbDocumentBlueprintServerDataSource
|
||||
*/
|
||||
async read(unique: string) {
|
||||
async read(unique: string): Promise<UmbDataSourceResponse<UmbDocumentBlueprintDetailModel>> {
|
||||
if (!unique) throw new Error('Unique is missing');
|
||||
|
||||
const { data, error } = await tryExecuteAndNotify(
|
||||
@@ -86,35 +87,24 @@ export class UmbDocumentBlueprintServerDataSource implements UmbDetailDataSource
|
||||
return { error };
|
||||
}
|
||||
|
||||
// TODO: make data mapper to prevent errors
|
||||
const document: UmbDocumentBlueprintDetailModel = {
|
||||
entityType: UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE,
|
||||
unique: data.id,
|
||||
values: data.values.map((value) => {
|
||||
return {
|
||||
editorAlias: value.editorAlias,
|
||||
culture: value.culture || null,
|
||||
segment: value.segment || null,
|
||||
alias: value.alias,
|
||||
value: value.value,
|
||||
};
|
||||
}),
|
||||
variants: data.variants.map((variant) => {
|
||||
return {
|
||||
state: variant.state,
|
||||
culture: variant.culture || null,
|
||||
segment: variant.segment || null,
|
||||
name: variant.name,
|
||||
publishDate: variant.publishDate || null,
|
||||
createDate: variant.createDate,
|
||||
updateDate: variant.updateDate,
|
||||
};
|
||||
}),
|
||||
documentType: {
|
||||
unique: data.documentType.id,
|
||||
collection: data.documentType.collection ? { unique: data.documentType.collection.id } : null,
|
||||
},
|
||||
};
|
||||
const document = this.#createDocumentBlueprintDetailModel(data);
|
||||
|
||||
return { data: document };
|
||||
}
|
||||
|
||||
async scaffoldByUnique(unique: string): Promise<UmbDataSourceResponse<UmbDocumentBlueprintDetailModel>> {
|
||||
if (!unique) throw new Error('Unique is missing');
|
||||
|
||||
const { data, error } = await tryExecuteAndNotify(
|
||||
this.#host,
|
||||
DocumentBlueprintService.getDocumentBlueprintByIdScaffold({ id: unique }),
|
||||
);
|
||||
|
||||
if (error || !data) {
|
||||
return { error };
|
||||
}
|
||||
|
||||
const document = this.#createDocumentBlueprintDetailModel(data);
|
||||
|
||||
return { data: document };
|
||||
}
|
||||
@@ -196,4 +186,35 @@ export class UmbDocumentBlueprintServerDataSource implements UmbDetailDataSource
|
||||
// TODO: update to delete when implemented
|
||||
return tryExecuteAndNotify(this.#host, DocumentBlueprintService.deleteDocumentBlueprintById({ id: unique }));
|
||||
}
|
||||
|
||||
#createDocumentBlueprintDetailModel(data: DocumentBlueprintResponseModel): UmbDocumentBlueprintDetailModel {
|
||||
return {
|
||||
entityType: UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE,
|
||||
unique: data.id,
|
||||
values: data.values.map((value) => {
|
||||
return {
|
||||
editorAlias: value.editorAlias,
|
||||
culture: value.culture || null,
|
||||
segment: value.segment || null,
|
||||
alias: value.alias,
|
||||
value: value.value,
|
||||
};
|
||||
}),
|
||||
variants: data.variants.map((variant) => {
|
||||
return {
|
||||
state: variant.state,
|
||||
culture: variant.culture || null,
|
||||
segment: variant.segment || null,
|
||||
name: variant.name,
|
||||
publishDate: variant.publishDate || null,
|
||||
createDate: variant.createDate,
|
||||
updateDate: variant.updateDate,
|
||||
};
|
||||
}),
|
||||
documentType: {
|
||||
unique: data.documentType.id,
|
||||
collection: data.documentType.collection ? { unique: data.documentType.collection.id } : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -48,7 +48,10 @@ export class UmbDocumentDuplicateToModalElement extends UmbModalBaseElement<
|
||||
<uui-box id="tree-box" headline="Duplicate to">
|
||||
<umb-tree
|
||||
alias=${UMB_DOCUMENT_TREE_ALIAS}
|
||||
.props=${{ expandTreeRoot: true }}
|
||||
.props=${{
|
||||
expandTreeRoot: true,
|
||||
hideTreeItemActions: true,
|
||||
}}
|
||||
@selection-change=${this.#onTreeSelectionChange}></umb-tree>
|
||||
</uui-box>
|
||||
<uui-box headline="Options">
|
||||
|
||||
+6
-4
@@ -211,14 +211,16 @@ export class UmbDocumentWorkspaceContext
|
||||
async create(parent: UmbEntityModel, documentTypeUnique: string, blueprintUnique?: string) {
|
||||
if (blueprintUnique) {
|
||||
const blueprintRepository = new UmbDocumentBlueprintDetailRepository(this);
|
||||
const { data } = await blueprintRepository.requestByUnique(blueprintUnique);
|
||||
const { data } = await blueprintRepository.scaffoldByUnique(blueprintUnique);
|
||||
|
||||
if (!data) throw new Error('Blueprint data is missing');
|
||||
|
||||
return this.createScaffold({
|
||||
parent,
|
||||
preset: {
|
||||
documentType: data?.documentType,
|
||||
values: data?.values,
|
||||
variants: data?.variants as Array<UmbDocumentVariantModel>,
|
||||
documentType: data.documentType,
|
||||
values: data.values,
|
||||
variants: data.variants as Array<UmbDocumentVariantModel>,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+6
-1
@@ -78,7 +78,12 @@ export class UmbInputImageCropperFieldElement extends UmbLitElement {
|
||||
|
||||
get source(): string {
|
||||
if (this.src) {
|
||||
return `${this._serverUrl}${this.src}`;
|
||||
// Test that URL is relative:
|
||||
if (this.src.startsWith('/')) {
|
||||
return `${this._serverUrl}${this.src}`;
|
||||
} else {
|
||||
return this.src;
|
||||
}
|
||||
}
|
||||
|
||||
return this.fileDataUrl ?? '';
|
||||
|
||||
+3
@@ -461,6 +461,7 @@ export function getMimeTypeFromExtension(extension: string): string | null {
|
||||
'.onetoc2': 'application/onenote',
|
||||
'.opf': 'application/oebps-package+xml',
|
||||
'.oprc': 'application/vnd.palm',
|
||||
'.opus': 'audio/ogg',
|
||||
'.org': 'application/vnd.lotus-organizer',
|
||||
'.osf': 'application/vnd.yamaha.openscoreformat',
|
||||
'.osfpvg': 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
|
||||
@@ -744,6 +745,8 @@ export function getMimeTypeFromExtension(extension: string): string | null {
|
||||
'.wbxml': 'application/vnd.wap.wbxml',
|
||||
'.wcm': 'application/vnd.ms-works',
|
||||
'.wdb': 'application/vnd.ms-works',
|
||||
'.weba': 'audio/webm',
|
||||
'.webm': 'video/webm',
|
||||
'.webp': 'image/webp',
|
||||
'.wiz': 'application/msword',
|
||||
'.wks': 'application/vnd.ms-works',
|
||||
|
||||
@@ -126,6 +126,7 @@ export class UmbDropzoneMediaElement extends UmbInputDropzoneElement {
|
||||
z-index: 100;
|
||||
border-radius: var(--uui-border-radius);
|
||||
border: 1px solid var(--uui-color-focus);
|
||||
box-sizing:border-box;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { umbDataTypeMockDb } from '../../../../../mocks/data/data-type/data-type
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { Meta } from '@storybook/web-components';
|
||||
|
||||
import './property-editor-ui-tiny-mce-stylesheets-configuration.element.js';
|
||||
import './property-editor-ui-stylesheet-picker.element.js';
|
||||
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
|
||||
|
||||
const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as UmbDataTypeDetailModel;
|
||||
@@ -10,7 +10,7 @@ const dataTypeData = umbDataTypeMockDb.read('dt-richTextEditor') as unknown as U
|
||||
export default {
|
||||
title: 'Property Editor UIs/Stylesheet Picker',
|
||||
component: 'umb-property-editor-ui-stylesheet-picker',
|
||||
id: 'umb-property-editor-ui-sstylesheet-picker',
|
||||
id: 'umb-property-editor-ui-stylesheet-picker',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview = ({ value }: any) =>
|
||||
|
||||
+28
-3
@@ -1,6 +1,15 @@
|
||||
import type { UmbTiptapExtensionApi } from '../../extensions/types.js';
|
||||
import type { UmbTiptapStatusbarValue, UmbTiptapToolbarValue } from '../types.js';
|
||||
import { css, customElement, html, map, property, state, unsafeCSS, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
repeat,
|
||||
state,
|
||||
unsafeCSS,
|
||||
when,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { Editor } from '@umbraco-cms/backoffice/external/tiptap';
|
||||
@@ -16,6 +25,12 @@ import './tiptap-statusbar.element.js';
|
||||
|
||||
const TIPTAP_CORE_EXTENSION_ALIAS = 'Umb.Tiptap.RichTextEssentials';
|
||||
|
||||
/**
|
||||
* The root path for the stylesheets on the server.
|
||||
* This is used to load the stylesheets from the server as a workaround until the server supports virtual paths.
|
||||
*/
|
||||
const STYLESHEET_ROOT_PATH = '/css';
|
||||
|
||||
@customElement('umb-input-tiptap')
|
||||
export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof UmbLitElement, string>(UmbLitElement) {
|
||||
#stylesheets = new Set(['/umbraco/backoffice/css/rte-content.css']);
|
||||
@@ -129,7 +144,13 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
|
||||
|
||||
const stylesheets = this.configuration?.getValueByAlias<Array<string>>('stylesheets');
|
||||
if (stylesheets?.length) {
|
||||
stylesheets.forEach((x) => this.#stylesheets.add(x));
|
||||
stylesheets.forEach((stylesheet) => {
|
||||
const linkHref =
|
||||
stylesheet.startsWith('http') || stylesheet.startsWith(STYLESHEET_ROOT_PATH)
|
||||
? stylesheet
|
||||
: `${STYLESHEET_ROOT_PATH}${stylesheet}`;
|
||||
this.#stylesheets.add(linkHref);
|
||||
});
|
||||
}
|
||||
|
||||
this._toolbar = this.configuration?.getValueByAlias<UmbTiptapToolbarValue>('toolbar') ?? [[[]]];
|
||||
@@ -182,7 +203,11 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
|
||||
#renderStyles() {
|
||||
if (!this._styles?.length) return;
|
||||
return html`
|
||||
${map(this.#stylesheets, (stylesheet) => html`<link rel="stylesheet" href=${stylesheet} />`)}
|
||||
${repeat(
|
||||
this.#stylesheets,
|
||||
(stylesheet) => stylesheet,
|
||||
(stylesheet) => html`<link rel="stylesheet" href="${stylesheet}" />`,
|
||||
)}
|
||||
<style>
|
||||
${this._styles.map((style) => unsafeCSS(style))}
|
||||
</style>
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@
|
||||
"@umbraco-cms/backoffice": "15.2.1",
|
||||
"msw": "^2.7.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3772,9 +3772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
|
||||
"integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz",
|
||||
"integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"@umbraco-cms/backoffice": "15.2.1",
|
||||
"msw": "^2.7.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.5",
|
||||
"vite": "^6.2.6",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
},
|
||||
"msw": {
|
||||
|
||||
+9
-10
@@ -7,8 +7,8 @@
|
||||
"name": "acceptancetest",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.31",
|
||||
"@umbraco/playwright-testhelpers": "^15.0.44",
|
||||
"@umbraco/json-models-builders": "^2.0.33",
|
||||
"@umbraco/playwright-testhelpers": "^15.0.56",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"node-fetch": "^2.6.7"
|
||||
@@ -58,21 +58,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/json-models-builders": {
|
||||
"version": "2.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.32.tgz",
|
||||
"integrity": "sha512-Aw7yBu8ePNxdjS7Q61j5KPFsiOS+IGCYxBX0H4KWbjXTdvL/PsB98KiqbDHHKFnp0fF1b2ffwJAI6jmvnxPBzg==",
|
||||
"license": "MIT",
|
||||
"version": "2.0.33",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.33.tgz",
|
||||
"integrity": "sha512-FAQxQIHoY6PGxWuodp4LSQxNufnOiqnaRNmtG8Ejn01r9lJJdA27CJKJ0bCs8U0W3cdN+Z+j7jBLd8H025/THw==",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/playwright-testhelpers": {
|
||||
"version": "15.0.44",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.44.tgz",
|
||||
"integrity": "sha512-rbStBJG0bdVfs9pzEjAhArhcPoQbuvrUpvDJLbEMBcg54lRFH0I5nzcAimdZaY4UWh2r0i9ANfjv9E25NJxRUg==",
|
||||
"version": "15.0.56",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.56.tgz",
|
||||
"integrity": "sha512-QPLNLnS96QvkCPpumpOwn9ufgIWgsv4W0UQENTU63pMniHPYfaflFMafI4qjM/euwMyXHH7k9+c4k/tGew2EQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "2.0.32",
|
||||
"@umbraco/json-models-builders": "2.0.33",
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.31",
|
||||
"@umbraco/playwright-testhelpers": "^15.0.44",
|
||||
"@umbraco/json-models-builders": "^2.0.33",
|
||||
"@umbraco/playwright-testhelpers": "^15.0.56",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"node-fetch": "^2.6.7"
|
||||
|
||||
@@ -43,14 +43,43 @@ export default defineConfig({
|
||||
testMatch: '**/*.setup.ts',
|
||||
},
|
||||
{
|
||||
name: 'chromium',
|
||||
name: 'defaultConfig',
|
||||
testMatch: 'DefaultConfig/**',
|
||||
dependencies: ['setup'],
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// Use prepared auth state.
|
||||
ignoreHTTPSErrors: true,
|
||||
storageState: STORAGE_STATE,
|
||||
storageState: STORAGE_STATE
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'deliveryApi',
|
||||
testMatch: 'DeliveryApi/**',
|
||||
dependencies: ['setup'],
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// Use prepared auth state.
|
||||
ignoreHTTPSErrors: true,
|
||||
storageState: STORAGE_STATE
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalLoginAzureADB2C',
|
||||
testMatch: 'ExternalLogin/AzureADB2C/**',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// Use prepared auth state.
|
||||
ignoreHTTPSErrors: true,
|
||||
}
|
||||
},
|
||||
// This project is used to test the install steps, for that we do not need to authenticate.
|
||||
{
|
||||
name: 'unattendedInstallConfig',
|
||||
testMatch: 'UnattendedInstallConfig/**',
|
||||
use: {
|
||||
...devices['Desktop Chrome']
|
||||
}
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import {test} from "@umbraco/playwright-testhelpers";
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
test.describe('DataType tests', () => {
|
||||
let dataTypeId = '';
|
||||
let dataTypeFolderId = '';
|
||||
let copiedDataTypeId = '';
|
||||
|
||||
const dataTypeName = 'TestDataType';
|
||||
const folderName = 'TestDataTypeFolder';
|
||||
const editorAlias = 'Umbraco.DateTime';
|
||||
const editorUiAlias = 'Umb.PropertyEditorUi.DatePicker';
|
||||
const dataTypeData = [
|
||||
{
|
||||
"alias": "tester",
|
||||
"value": "Howdy"
|
||||
}
|
||||
];
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.dataType.ensureNameNotExists(dataTypeName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.dataType.ensureNameNotExists(dataTypeName);
|
||||
if (dataTypeFolderId != '') {
|
||||
await umbracoApi.dataType.ensureNameNotExists(folderName);
|
||||
}
|
||||
});
|
||||
|
||||
test('can create dataType', async ({umbracoApi}) => {
|
||||
// Act
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
|
||||
// Assert
|
||||
expect(umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can update dataType', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, []);
|
||||
const dataType = await umbracoApi.dataType.get(dataTypeId);
|
||||
dataType.values = dataTypeData;
|
||||
|
||||
// Act
|
||||
await umbracoApi.dataType.update(dataTypeId, dataType);
|
||||
|
||||
// Assert
|
||||
// Checks if the data type was updated
|
||||
const updatedDataType = await umbracoApi.dataType.get(dataTypeId);
|
||||
expect(updatedDataType.values).toEqual(dataTypeData);
|
||||
});
|
||||
|
||||
test('can delete dataType', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
await umbracoApi.dataType.delete(dataTypeId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('can move a dataType to a folder', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
await umbracoApi.dataType.ensureNameNotExists(folderName);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy();
|
||||
dataTypeFolderId = await umbracoApi.dataType.createFolder(folderName);
|
||||
expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
await umbracoApi.dataType.moveToFolder(dataTypeId, dataTypeFolderId);
|
||||
|
||||
// Assert
|
||||
// Checks if the datatype has the parentId of the folder
|
||||
const dataTypeInFolder = await umbracoApi.dataType.getChildren(dataTypeFolderId);
|
||||
expect(dataTypeInFolder[0].parent.id).toEqual(dataTypeFolderId);
|
||||
});
|
||||
|
||||
test('can copy a dataType to a folder', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
await umbracoApi.dataType.ensureNameNotExists(folderName);
|
||||
dataTypeId = await umbracoApi.dataType.create(dataTypeName, editorAlias, editorUiAlias, dataTypeData);
|
||||
dataTypeFolderId = await umbracoApi.dataType.createFolder(folderName);
|
||||
|
||||
const dataType = await umbracoApi.dataType.get(dataTypeId);
|
||||
const dataTypeFolder = await umbracoApi.dataType.getFolder(dataTypeFolderId);
|
||||
|
||||
// Act
|
||||
copiedDataTypeId = await umbracoApi.dataType.copyToFolder(dataType.id, dataTypeFolder.id);
|
||||
|
||||
// Assert
|
||||
const copiedDataType = await umbracoApi.dataType.get(copiedDataTypeId);
|
||||
expect(copiedDataType.name).toEqual(dataTypeName + ' (copy)');
|
||||
// Checks if both dataTypes exists
|
||||
expect(await umbracoApi.dataType.doesExist(dataTypeId)).toBeTruthy();
|
||||
expect(await umbracoApi.dataType.doesExist(copiedDataTypeId)).toBeTruthy();
|
||||
|
||||
// Clean
|
||||
await umbracoApi.dataType.delete(copiedDataTypeId);
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import {test} from "@umbraco/playwright-testhelpers";
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
test.describe('DataTypeFolder tests', () => {
|
||||
let dataTypeFolderId = '';
|
||||
const dataTypeFolderName = 'TestDataTypeFolder';
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.dataType.ensureNameNotExists(dataTypeFolderName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.dataType.ensureNameNotExists(dataTypeFolderName);
|
||||
});
|
||||
|
||||
test('can create a dataType folder', async ({umbracoApi}) => {
|
||||
// Act
|
||||
dataTypeFolderId = await umbracoApi.dataType.createFolder(dataTypeFolderName);
|
||||
|
||||
// Assert
|
||||
expect(umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can rename a dataType folder', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const wrongDataTypeFolderName = 'WrongFolderName';
|
||||
dataTypeFolderId = await umbracoApi.dataType.createFolder(wrongDataTypeFolderName);
|
||||
|
||||
// Act
|
||||
await umbracoApi.dataType.renameFolder(dataTypeFolderId, dataTypeFolderName);
|
||||
|
||||
// Assert
|
||||
expect(umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
|
||||
// Checks if the dataType folder was updated
|
||||
const newDataTypeFolderName = await umbracoApi.dataType.getFolder(dataTypeFolderId);
|
||||
expect(newDataTypeFolderName.name).toEqual(dataTypeFolderName);
|
||||
});
|
||||
|
||||
test('can delete a dataType folder', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
await umbracoApi.dataType.createFolder(dataTypeFolderName);
|
||||
expect(umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
await umbracoApi.dataType.delete(dataTypeFolderId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderId)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import {test} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
test.describe('Dictionary tests', () => {
|
||||
let dictionaryId = '';
|
||||
const dictionaryName = 'TestDictionary';
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.dictionary.ensureNameNotExists(dictionaryName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.dictionary.ensureNameNotExists(dictionaryName);
|
||||
});
|
||||
|
||||
test('can create a dictionary', async ({umbracoApi}) => {
|
||||
// Act
|
||||
dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(dictionaryName);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can update a dictionary', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const wrongDictionaryName = 'WrongTestDictionary';
|
||||
dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(wrongDictionaryName);
|
||||
expect(await umbracoApi.dictionary.doesNameExist(wrongDictionaryName)).toBeTruthy();
|
||||
const dictionary = await umbracoApi.dictionary.get(dictionaryId);
|
||||
|
||||
// Act
|
||||
dictionary.name = dictionaryName;
|
||||
await umbracoApi.dictionary.update(dictionaryId, dictionary);
|
||||
|
||||
// Assert
|
||||
// Checks if the dictionary was updated
|
||||
const newDictionary = await umbracoApi.dictionary.get(dictionaryId);
|
||||
expect(newDictionary.name).toEqual(dictionaryName);
|
||||
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can delete a dictionary', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
dictionaryId = await umbracoApi.dictionary.create(dictionaryName);
|
||||
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
await umbracoApi.dictionary.delete(dictionaryId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('can create a dictionary item in a dictionary', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const childDictionaryName = 'ChildDictionary';
|
||||
await umbracoApi.dictionary.ensureNameNotExists(dictionaryName);
|
||||
dictionaryId = await umbracoApi.dictionary.create(dictionaryName);
|
||||
|
||||
// Act
|
||||
await umbracoApi.dictionary.create(childDictionaryName, [], dictionaryId);
|
||||
|
||||
// Assert
|
||||
// Checks if the parent dictionary contains the child dictionary
|
||||
const dictionaryChildren = await umbracoApi.dictionary.getChildren(dictionaryId);
|
||||
expect(dictionaryChildren[0].name).toEqual(childDictionaryName);
|
||||
});
|
||||
|
||||
test('can export a dictionary', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
dictionaryId = await umbracoApi.dictionary.create(dictionaryName);
|
||||
expect(await umbracoApi.dictionary.doesExist(dictionaryId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
const exportResponse = await umbracoApi.dictionary.export(dictionaryId, false);
|
||||
|
||||
// Assert
|
||||
// Checks if the .udt file is exported
|
||||
expect(exportResponse.headers()['content-disposition']).toContain(".udt");
|
||||
});
|
||||
|
||||
test('can import a dictionary', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const temporaryFileId = crypto.randomUUID();
|
||||
const fileName = 'TestSingleDictionary.udt';
|
||||
const mimeType = 'application/octet-stream';
|
||||
const filePath = './fixtures/dictionary/TestSingleDictionary.udt';
|
||||
// This variable must not be changed as it is declared in the file TestDictionary.udt
|
||||
const importDictionaryName = 'TestImportDictionary';
|
||||
|
||||
// Create temporary file
|
||||
await umbracoApi.temporaryFile.create(temporaryFileId, fileName, mimeType, filePath);
|
||||
expect(await umbracoApi.temporaryFile.doesExist(temporaryFileId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
const importResponse = await umbracoApi.dictionary.import(temporaryFileId, null);
|
||||
|
||||
// Assert
|
||||
expect(importResponse.status).toBeTruthy();
|
||||
expect(await umbracoApi.dictionary.doesNameExist(importDictionaryName)).toBeTruthy();
|
||||
|
||||
// Clean
|
||||
await umbracoApi.temporaryFile.delete(temporaryFileId);
|
||||
});
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
import {test} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
test.describe('Documents tests', () => {
|
||||
let documentTypeId = '';
|
||||
let documentId = '';
|
||||
const documentName = 'TestDocument';
|
||||
const documentTypeName = 'TestDocumentType';
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(documentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
});
|
||||
|
||||
test('can create a document', async ({umbracoApi}) => {
|
||||
// Act
|
||||
await umbracoApi.document.createDefaultDocument(documentName, documentTypeId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.document.doesNameExist(documentName)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can delete a document', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId);
|
||||
expect(umbracoApi.document.doesExist(documentId)).toBeTruthy();
|
||||
|
||||
// Act
|
||||
await umbracoApi.document.delete(documentId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.document.doesNameExist(documentName)).toBeFalsy();
|
||||
});
|
||||
|
||||
test('can update a document', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const wrongName = 'WrongDocument';
|
||||
documentId = await umbracoApi.document.createDefaultDocument(wrongName, documentTypeId);
|
||||
expect(await umbracoApi.document.doesNameExist(wrongName)).toBeTruthy();
|
||||
let documentData = await umbracoApi.document.get(documentId);
|
||||
documentData.variants[0].name = documentName;
|
||||
|
||||
// Act
|
||||
await umbracoApi.document.update(documentData.id, documentData);
|
||||
|
||||
// Assert
|
||||
const updatedDocumentData = await umbracoApi.document.get(documentId);
|
||||
expect(updatedDocumentData.variants[0].name).toEqual(documentName);
|
||||
});
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
import {test} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
test.describe('Language tests', () => {
|
||||
const languageNameDanish = 'Dansk';
|
||||
const isoCodeDanish = 'da-DK';
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.language.ensureNameNotExists(languageNameDanish);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.language.ensureNameNotExists(languageNameDanish);
|
||||
});
|
||||
|
||||
test('can create a language', async ({umbracoApi}) => {
|
||||
// Act
|
||||
await umbracoApi.language.create(languageNameDanish, false, false, isoCodeDanish);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can update a language', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
const wrongLanguageName = 'densk';
|
||||
await umbracoApi.language.create(wrongLanguageName, false, false, isoCodeDanish);
|
||||
const language = await umbracoApi.language.get(isoCodeDanish);
|
||||
|
||||
// Act
|
||||
language.name = languageNameDanish;
|
||||
await umbracoApi.language.update(isoCodeDanish, language);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeTruthy();
|
||||
// Checks if the language name was updated
|
||||
const updatedLanguage = await umbracoApi.language.get(isoCodeDanish);
|
||||
expect(updatedLanguage.name).toEqual(languageNameDanish);
|
||||
});
|
||||
|
||||
test('can delete a language', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
await umbracoApi.language.create(languageNameDanish, false, false, isoCodeDanish);
|
||||
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeTruthy();
|
||||
|
||||
//Act
|
||||
await umbracoApi.language.delete(isoCodeDanish);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.language.doesExist(isoCodeDanish)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
import {test} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
test.describe('Member tests', () => {
|
||||
let memberId = '';
|
||||
let memberTypeId = '';
|
||||
const memberName = 'TestMember';
|
||||
const memberTypeName = 'TestMemberType';
|
||||
const username = 'testmember';
|
||||
const email = 'testmember@acceptance.test';
|
||||
const password = '0123456789';
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.memberType.ensureNameNotExists(memberTypeName);
|
||||
await umbracoApi.member.ensureNameNotExists(memberName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.memberType.ensureNameNotExists(memberTypeName);
|
||||
await umbracoApi.member.ensureNameNotExists(memberName);
|
||||
});
|
||||
|
||||
test('can create a member', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
memberTypeId = await umbracoApi.memberType.createDefaultMemberType(memberTypeName);
|
||||
|
||||
// Act
|
||||
memberId = await umbracoApi.member.createDefaultMember(memberName, memberTypeId, email, username, password);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.member.doesExist(memberId)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('can delete a member', async ({umbracoApi}) => {
|
||||
// Arrange
|
||||
memberTypeId = await umbracoApi.memberType.createDefaultMemberType(memberTypeName);
|
||||
memberId = await umbracoApi.member.createDefaultMember(memberName, memberTypeId, email, username, password);
|
||||
|
||||
// Act
|
||||
await umbracoApi.member.delete(memberId);
|
||||
|
||||
// Assert
|
||||
expect(await umbracoApi.member.doesExist(memberId)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user