Skip to content

Commit

Permalink
Adding @promotion decorator and learnMoreDocs validation (#287)
Browse files Browse the repository at this point in the history
adding a decorator called @promotion which helps us know which
apiVersion for RP should be deploy to Portal.

also adding validation rule for @about.learnMoreDocs checking if the
links are starting with https://

---------

Co-authored-by: Timothee Guerin <timothee.guerin@outlook.com>
  • Loading branch information
yejee94 and timotheeguerin authored Feb 22, 2024
1 parent 8c958fc commit b556e5a
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 9 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/yejeelee-promotions-2024-1-21-1-4-17.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-azure-portal-core"
---

adding new decorator called `@promotion` and adding validation rule for `@about.LearnMoreDocs`
8 changes: 8 additions & 0 deletions docs/libraries/azure-portal-core/reference/data-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ Options for marketplaceOffer
```typespec
model Azure.Portal.marketplaceOfferOptions
```

### `PromotionOptions` {#Azure.Portal.PromotionOptions}

Options for promotion of ARM resource.

```typespec
model Azure.Portal.PromotionOptions
```
19 changes: 19 additions & 0 deletions docs/libraries/azure-portal-core/reference/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,22 @@ Provides a Model marketplace offer information of ARM resource.
| Name | Type | Description |
| ------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
| options | `model Azure.Portal.marketplaceOfferOptions` | Property options provides marketplace offer information of the resourceType. |

### `@promotion` {#@Azure.Portal.promotion}

Provides a Model customizing deployment promotion apiVersion for ARM resource.
The apiVersion will be used as a version to deploy to Portal.

```typespec
@Azure.Portal.promotion(options: Azure.Portal.PromotionOptions)
```

#### Target

`Model`

#### Parameters

| Name | Type | Description |
| ------- | ------------------------------------- | -------------------------------------------------------------------- |
| options | `model Azure.Portal.PromotionOptions` | Property options provides promotion information of the resourceType. |
2 changes: 2 additions & 0 deletions docs/libraries/azure-portal-core/reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ npm install --save-peer @azure-tools/typespec-azure-portal-core
- [`@browse`](./decorators.md#@Azure.Portal.browse)
- [`@displayName`](./decorators.md#@Azure.Portal.displayName)
- [`@marketplaceOffer`](./decorators.md#@Azure.Portal.marketplaceOffer)
- [`@promotion`](./decorators.md#@Azure.Portal.promotion)

### Models

- [`AboutOptions`](./data-types.md#Azure.Portal.AboutOptions)
- [`BrowseOptions`](./data-types.md#Azure.Portal.BrowseOptions)
- [`FilePath`](./data-types.md#Azure.Portal.FilePath)
- [`marketplaceOfferOptions`](./data-types.md#Azure.Portal.marketplaceOfferOptions)
- [`PromotionOptions`](./data-types.md#Azure.Portal.PromotionOptions)
20 changes: 20 additions & 0 deletions packages/typespec-azure-portal-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ npm install @azure-tools/typespec-azure-portal-core
- [`@browse`](#@browse)
- [`@displayName`](#@displayname)
- [`@marketplaceOffer`](#@marketplaceoffer)
- [`@promotion`](#@promotion)

#### `@about`

Expand Down Expand Up @@ -88,3 +89,22 @@ Provides a Model marketplace offer information of ARM resource.
| Name | Type | Description |
| ------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
| options | `model Azure.Portal.marketplaceOfferOptions` | Property options provides marketplace offer information of the resourceType. |

#### `@promotion`

Provides a Model customizing deployment promotion apiVersion for ARM resource.
The apiVersion will be used as a version to deploy to Portal.

```typespec
@Azure.Portal.promotion(options: Azure.Portal.PromotionOptions)
```

##### Target

`Model`

##### Parameters

| Name | Type | Description |
| ------- | ------------------------------------- | -------------------------------------------------------------------- |
| options | `model Azure.Portal.PromotionOptions` | Property options provides promotion information of the resourceType. |
13 changes: 13 additions & 0 deletions packages/typespec-azure-portal-core/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ extern dec about(target: Model, options: AboutOptions);
*/
extern dec browse(target: Model, options: BrowseOptions);

/**
* Provides a Model customizing deployment promotion apiVersion for ARM resource.
* The apiVersion will be used as a version to deploy to Portal.
* @param options Property options provides promotion information of the resourceType.
*/
extern dec promotion(target: Model, options: PromotionOptions);

/**
* Provides a Model marketplace offer information of ARM resource.
* @param options Property options provides marketplace offer information of the resourceType.
Expand Down Expand Up @@ -63,3 +70,9 @@ model AboutOptions {
/** Set of links which can help learn more about the resource */
learnMoreDocs?: string[];
}

/** Options for promotion of ARM resource. */
model PromotionOptions {
apiVersion: string | EnumMember;
autoUpdate?: boolean;
}
98 changes: 96 additions & 2 deletions packages/typespec-azure-portal-core/src/decorators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ArmResourceKind, getArmResourceKind } from "@azure-tools/typespec-azure-resource-manager";
import {
BooleanLiteral,
CompilerHost,
DecoratorContext,
Model,
Expand All @@ -8,13 +9,15 @@ import {
StringLiteral,
Type,
getDirectoryPath,
getService,
getSourceLocation,
normalizePath,
resolvePath,
validateDecoratorUniqueOnNode,
} from "@typespec/compiler";
import { VersionMap, getVersions } from "@typespec/versioning";
import { PortalCoreKeys, reportDiagnostic } from "./lib.js";
import { AboutOptions, BrowseOptions, marketplaceOfferOptions } from "./types.js";
import { AboutOptions, BrowseOptions, PromotionOptions, marketplaceOfferOptions } from "./types.js";

/**
* This is a Browse decorator which will be use to put more info on the browse view.
Expand Down Expand Up @@ -52,6 +55,72 @@ export function $browse(context: DecoratorContext, target: Model, options: Model
}
}

export function $promotion(context: DecoratorContext, target: Model, options: Model) {
const { program } = context;
validateDecoratorUniqueOnNode(context, target, $promotion);
checkIsArmResource(program, target, "promotion");
if (options && options.properties) {
const apiVersion = options.properties.get("apiVersion");
const currentApiVersion = (apiVersion?.type as StringLiteral).value;
if (!checkIsValidApiVersion(program, target, currentApiVersion)) {
return;
}
const versions = getVersions(program, target);
const autoUpdateProp = options.properties.get("autoUpdate");
const autoUpdate = autoUpdateProp && (autoUpdateProp?.type as BooleanLiteral).value;
const promotionResult: PromotionOptions = {
apiVersion: currentApiVersion,
autoUpdate: autoUpdate ?? false,
};
if (versions && versions[1] && versions[1].size > 0) {
const versionsList = (versions[1] as VersionMap)
.getVersions()
.map((version) => version.value);
if (!versionsList.includes(currentApiVersion)) {
reportDiagnostic(program, {
code: "invalid-apiversion",
messageId: "versionsList",
format: {
version: currentApiVersion,
},
target,
});
return;
}
} else if (target.namespace) {
const service = getService(program, target.namespace);
if (service?.version && currentApiVersion !== service.version) {
reportDiagnostic(program, {
code: "invalid-apiversion",
messageId: "serviceVersion",
format: {
version: currentApiVersion,
},
target,
});
return;
}
}
program.stateMap(PortalCoreKeys.promotion).set(target, promotionResult);
}
}

export function checkIsValidApiVersion(program: Program, target: Model, version: string) {
const pattern = /^(\d{4}-\d{2}-\d{2})(|-preview)$/;
if (version.match(pattern) == null) {
reportDiagnostic(program, {
code: "invalid-apiversion",
messageId: "promotionVersion",
format: {
version: version,
},
target,
});
return false;
}
return true;
}

export async function isFileExist(host: CompilerHost, filePath: string) {
try {
const stats = await host.stat(filePath);
Expand All @@ -64,6 +133,10 @@ export async function isFileExist(host: CompilerHost, filePath: string) {
}
}

export function getPromotion(program: Program, target: Type) {
return program.stateMap(PortalCoreKeys.promotion).get(target);
}

export function getBrowse(program: Program, target: Type) {
return program.stateMap(PortalCoreKeys.browse).get(target);
}
Expand All @@ -75,7 +148,7 @@ export function getBrowseArgQuery(program: Program, target: Type) {
export function checkIsArmResource(
program: Program,
target: Model,
decoratorName: "browse" | "about" | "marketplaceOffer"
decoratorName: "browse" | "about" | "marketplaceOffer" | "promotion"
) {
if (
getArmResourceKind(target) !== ("Tracked" as ArmResourceKind) &&
Expand Down Expand Up @@ -129,6 +202,9 @@ export function $about(context: DecoratorContext, target: Model, options: Model)
}
if (learnMoreDocs) {
if (learnMoreDocs.type.kind === "Tuple") {
if (!checkIsValidLinks(program, target, learnMoreDocs.type.values)) {
return;
}
const learnMoreDocsValues = learnMoreDocs.type.values
.filter((value) => value.kind === "String")
.map((value: Type) => (value as StringLiteral).value);
Expand All @@ -151,6 +227,24 @@ export function $about(context: DecoratorContext, target: Model, options: Model)
program.stateMap(PortalCoreKeys.about).set(target, aboutOptionsResult);
}

function checkIsValidLinks(program: Program, target: Model, links: Type[]) {
let valid = true;
links.forEach((value) => {
const pattern = /^https:\/\//;
if (!(value as StringLiteral).value.match(pattern)) {
reportDiagnostic(program, {
code: "invalid-link",
format: {
link: (value as StringLiteral).value,
},
target,
});
valid = false;
}
});
return valid;
}

export function getAbout(program: Program, target: Type) {
return program.stateMap(PortalCoreKeys.about).get(target);
}
Expand Down
15 changes: 15 additions & 0 deletions packages/typespec-azure-portal-core/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,27 @@ export const $lib = createTypeSpecLibrary({
default: `essentials can be only used 5 times in ModelProperty.`,
},
},
"invalid-apiversion": {
severity: "error",
messages: {
versionsList: paramMessage`@promotion apiVersion ${"version"} is not listed on ARM service API Version lists`,
serviceVersion: paramMessage`@promotion apiVersion ${"version"} is not same as the @service.version`,
promotionVersion: paramMessage`@promotion apiVersion ${"version"} is invalid, should be yyyy-mm-dd or yyyy-mm-dd-preview format`,
},
},
"invalid-link": {
severity: "error",
messages: {
default: paramMessage`@about learnMoreDocs ${"link"} does not start with https://`,
},
},
},
state: {
browse: { description: "State for the @browse decorator" },
about: { description: "State for the @about decorator" },
marketplaceOffer: { description: "State for the @marketplaceOffer decorator" },
displayName: { description: "State for the @displayName decorator" },
promotion: { description: "State for the @promotion decorator" },
},
} as const);

Expand Down
7 changes: 7 additions & 0 deletions packages/typespec-azure-portal-core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { EnumMember } from "@typespec/compiler";

export interface FilePath {
filePath: string;
}
Expand All @@ -17,3 +19,8 @@ export interface BrowseOptions {
export interface marketplaceOfferOptions {
id?: string;
}

export interface PromotionOptions {
readonly apiVersion: string | EnumMember;
readonly autoUpdate?: boolean;
}
Loading

0 comments on commit b556e5a

Please sign in to comment.