devops-plugin-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/@danieli-automation/create-devops-plugin/package.json +30 -0
  2. package/@danieli-automation/create-devops-plugin/src/cli.ts +136 -0
  3. package/@danieli-automation/create-devops-plugin/src/index.ts +37 -0
  4. package/@danieli-automation/create-devops-plugin/src/template/files/apiIndexMock.ts +10 -0
  5. package/@danieli-automation/create-devops-plugin/src/template/files/appHtml.ts +25 -0
  6. package/@danieli-automation/create-devops-plugin/src/template/files/appStyles.ts +22 -0
  7. package/@danieli-automation/create-devops-plugin/src/template/files/appTsx.ts +41 -0
  8. package/@danieli-automation/create-devops-plugin/src/template/files/envExample.ts +11 -0
  9. package/@danieli-automation/create-devops-plugin/src/template/files/gitignore.ts +13 -0
  10. package/@danieli-automation/create-devops-plugin/src/template/files/index.ts +51 -0
  11. package/@danieli-automation/create-devops-plugin/src/template/files/packageJson.ts +60 -0
  12. package/@danieli-automation/create-devops-plugin/src/template/files/readme.ts +30 -0
  13. package/@danieli-automation/create-devops-plugin/src/template/files/sdkMock.ts +11 -0
  14. package/@danieli-automation/create-devops-plugin/src/template/files/testSetup.ts +16 -0
  15. package/@danieli-automation/create-devops-plugin/src/template/files/tsconfigJson.ts +30 -0
  16. package/@danieli-automation/create-devops-plugin/src/template/files/vitestConfig.ts +57 -0
  17. package/@danieli-automation/create-devops-plugin/src/template/files/webpackAppConfig.ts +31 -0
  18. package/@danieli-automation/create-devops-plugin/src/template/files/webpackCommonConfig.ts +116 -0
  19. package/@danieli-automation/create-devops-plugin/src/template/files/webpackConfig.ts +15 -0
  20. package/@danieli-automation/create-devops-plugin/src/template/files.ts +1 -0
  21. package/@danieli-automation/create-devops-plugin/src/template/fs.ts +85 -0
  22. package/@danieli-automation/create-devops-plugin/src/template/manifest.ts +40 -0
  23. package/@danieli-automation/create-devops-plugin/src/template/npm.ts +22 -0
  24. package/@danieli-automation/create-devops-plugin/src/template/static/fonts/AzDevMDL2.woff +0 -0
  25. package/@danieli-automation/create-devops-plugin/src/template/static/fonts/bowtie.woff2 +0 -0
  26. package/@danieli-automation/create-devops-plugin/src/template/static/fonts/fabric-icons.woff +0 -0
  27. package/@danieli-automation/create-devops-plugin/src/template/static/fonts/fluent-filled-v1.1.293.woff2 +0 -0
  28. package/@danieli-automation/create-devops-plugin/src/template/static/fonts/fluent-regular-v1.1.293.woff2 +0 -0
  29. package/@danieli-automation/create-devops-plugin/src/template/static/images/DigiMetLogo.jpeg +0 -0
  30. package/@danieli-automation/create-devops-plugin/src/template/static/images/danieliAutomation.png +0 -0
  31. package/@danieli-automation/create-devops-plugin/src/template/static/images/danieliAutomationBlack.jpg +0 -0
  32. package/@danieli-automation/create-devops-plugin/src/template/static/images/danieli_digi_met_logo.jpeg +0 -0
  33. package/@danieli-automation/create-devops-plugin/src/template/static/images/logoSmallpng.png +0 -0
  34. package/@danieli-automation/create-devops-plugin/src/template/types.ts +14 -0
  35. package/@danieli-automation/create-devops-plugin/src/template/utils.ts +22 -0
  36. package/@danieli-automation/create-devops-plugin/tsconfig.json +8 -0
  37. package/@danieli-automation/devops-plugin-core/package.json +27 -0
  38. package/@danieli-automation/devops-plugin-core/src/core/azureClients.ts +18 -0
  39. package/@danieli-automation/devops-plugin-core/src/core/storage/createStore.ts +65 -0
  40. package/@danieli-automation/devops-plugin-core/src/core/storage/hooks/useCrossTeamSprintInstance.ts +145 -0
  41. package/@danieli-automation/devops-plugin-core/src/core/storage/hooks/useTaskOrder.ts +125 -0
  42. package/@danieli-automation/devops-plugin-core/src/core/storage/hooks/useWorkItemOrder.ts +86 -0
  43. package/@danieli-automation/devops-plugin-core/src/core/storage/index.ts +13 -0
  44. package/@danieli-automation/devops-plugin-core/src/core/storage/keys.ts +31 -0
  45. package/@danieli-automation/devops-plugin-core/src/core/storage/repositories/instance.ts +184 -0
  46. package/@danieli-automation/devops-plugin-core/src/core/storage/repositories/taskOrder.ts +59 -0
  47. package/@danieli-automation/devops-plugin-core/src/core/storage/repositories/workItemOrder.ts +60 -0
  48. package/@danieli-automation/devops-plugin-core/src/core/storage/stores.ts +18 -0
  49. package/@danieli-automation/devops-plugin-core/src/core/types/AdoWorkItemType.ts +18 -0
  50. package/@danieli-automation/devops-plugin-core/src/core/types/KVStoreType.ts +1 -0
  51. package/@danieli-automation/devops-plugin-core/src/core/types/ScopeType.ts +1 -0
  52. package/@danieli-automation/devops-plugin-core/src/core/types/SelectedProjectType.ts +8 -0
  53. package/@danieli-automation/devops-plugin-core/src/core/types/SortConfigType.ts +2 -0
  54. package/@danieli-automation/devops-plugin-core/src/core/types/instance/CreateInstanceInputType.ts +10 -0
  55. package/@danieli-automation/devops-plugin-core/src/core/types/instance/CrossSprintInstanceType.ts +20 -0
  56. package/@danieli-automation/devops-plugin-core/src/core/types/instance/DefaultInstanceType.ts +12 -0
  57. package/@danieli-automation/devops-plugin-core/src/core/types/instance/InstanceRowType.ts +18 -0
  58. package/@danieli-automation/devops-plugin-core/src/core/types/instance/UpdateInstanceInputType.ts +10 -0
  59. package/@danieli-automation/devops-plugin-core/src/core/types/taskOrder/TaskOrderMapType.ts +3 -0
  60. package/@danieli-automation/devops-plugin-core/src/core/types/taskOrder/TaskOrderType.ts +1 -0
  61. package/@danieli-automation/devops-plugin-core/src/core/types/workItemOrder/WorkItemOrderMapType.ts +3 -0
  62. package/@danieli-automation/devops-plugin-core/src/core/types/workItemOrder/WorkItemOrderType.ts +1 -0
  63. package/@danieli-automation/devops-plugin-core/src/index.ts +1 -0
  64. package/@danieli-automation/devops-plugin-core/src/pluginCore.ts +12 -0
  65. package/@danieli-automation/devops-plugin-core/tsconfig.json +16 -0
  66. package/@danieli-automation/devops-plugin-features/package.json +31 -0
  67. package/@danieli-automation/devops-plugin-features/src/app/stores/useUIStore.ts +12 -0
  68. package/@danieli-automation/devops-plugin-features/src/app/utils/date.ts +9 -0
  69. package/@danieli-automation/devops-plugin-features/src/app/utils/global.ts +9 -0
  70. package/@danieli-automation/devops-plugin-features/src/core/azureClients.ts +12 -0
  71. package/@danieli-automation/devops-plugin-features/src/features/instances/constants/InstanceConstant.ts +5 -0
  72. package/@danieli-automation/devops-plugin-features/src/features/instances/hooks/useInstancePermission.ts +127 -0
  73. package/@danieli-automation/devops-plugin-features/src/features/instances/stores/__tests__/useInstanceStore.test.ts +25 -0
  74. package/@danieli-automation/devops-plugin-features/src/features/instances/stores/types/InstanceStoreType.ts +7 -0
  75. package/@danieli-automation/devops-plugin-features/src/features/instances/stores/useInstanceStore.ts +9 -0
  76. package/@danieli-automation/devops-plugin-features/src/features/instances/types/CrossSprintInstanceType.ts +20 -0
  77. package/@danieli-automation/devops-plugin-features/src/features/instances/utils/instance.ts +55 -0
  78. package/@danieli-automation/devops-plugin-features/src/features/iterations/api/iterations.ts +298 -0
  79. package/@danieli-automation/devops-plugin-features/src/features/iterations/services/IterationService.ts +215 -0
  80. package/@danieli-automation/devops-plugin-features/src/features/iterations/types/IterationInfoType.ts +15 -0
  81. package/@danieli-automation/devops-plugin-features/src/features/iterations/utils/__tests__/iteration.test.ts +39 -0
  82. package/@danieli-automation/devops-plugin-features/src/features/iterations/utils/iteration.ts +132 -0
  83. package/@danieli-automation/devops-plugin-features/src/features/teams/api/projects.ts +79 -0
  84. package/@danieli-automation/devops-plugin-features/src/features/teams/api/teams.ts +29 -0
  85. package/@danieli-automation/devops-plugin-features/src/features/teams/api/users.ts +124 -0
  86. package/@danieli-automation/devops-plugin-features/src/features/teams/services/ProjectService.ts +80 -0
  87. package/@danieli-automation/devops-plugin-features/src/features/teams/types/SelectedProjectType.ts +8 -0
  88. package/@danieli-automation/devops-plugin-features/src/features/teams/types/TeamMemberType.ts +7 -0
  89. package/@danieli-automation/devops-plugin-features/src/features/teams/types/TeamRowSeedType.ts +6 -0
  90. package/@danieli-automation/devops-plugin-features/src/features/teams/types/UserSearchResultType.ts +8 -0
  91. package/@danieli-automation/devops-plugin-features/src/features/work-items/api/states.ts +24 -0
  92. package/@danieli-automation/devops-plugin-features/src/features/work-items/api/wiql.ts +146 -0
  93. package/@danieli-automation/devops-plugin-features/src/features/work-items/api/workItems.ts +193 -0
  94. package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/DefaultConsant.ts +43 -0
  95. package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/FieldConstant.ts +27 -0
  96. package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/StateConstant.ts +16 -0
  97. package/@danieli-automation/devops-plugin-features/src/features/work-items/constants/TypeConstant.ts +8 -0
  98. package/@danieli-automation/devops-plugin-features/src/features/work-items/services/WIQLService.ts +101 -0
  99. package/@danieli-automation/devops-plugin-features/src/features/work-items/services/WorkItemService.ts +54 -0
  100. package/@danieli-automation/devops-plugin-features/src/features/work-items/types/AdoWorkItemType.ts +18 -0
  101. package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/__tests__/filter.test.ts +87 -0
  102. package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/__tests__/workItem.test.ts +116 -0
  103. package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/filter.ts +189 -0
  104. package/@danieli-automation/devops-plugin-features/src/features/work-items/utils/workItem.ts +245 -0
  105. package/@danieli-automation/devops-plugin-features/src/index.ts +0 -0
  106. package/@danieli-automation/devops-plugin-features/src/test/mocks/azure-devops-extension-api/Work.ts +5 -0
  107. package/@danieli-automation/devops-plugin-features/tsconfig.json +18 -0
  108. package/@danieli-automation/devops-plugin-features/vitest.config.ts +21 -0
  109. package/README.md +55 -0
  110. package/devops-plugin-kit-0.1.0.tgz +0 -0
  111. package/package.json +29 -0
  112. package/tsconfig.base.json +15 -0
@@ -0,0 +1,18 @@
1
+ type ListBoxItem = {
2
+ id?: string;
3
+ text?: string;
4
+ [key: string]: unknown;
5
+ };
6
+
7
+ type DropdownSelection = unknown;
8
+
9
+ export type InstanceRowType = {
10
+ id: string;
11
+ selectedProjectKey?: string;
12
+ projectName?: string;
13
+ selectedTeamKey?: string;
14
+ teamName?: string;
15
+ teams: ListBoxItem[];
16
+ projectSelection: DropdownSelection;
17
+ teamSelection: DropdownSelection;
18
+ };
@@ -0,0 +1,10 @@
1
+ import { SelectedProjectType } from "core/types/SelectedProjectType";
2
+
3
+ export type UpdateInstanceInput = {
4
+ id: string;
5
+ name?: string;
6
+ description?: string;
7
+ projectTeamPairs?: SelectedProjectType[];
8
+ owners?: string[];
9
+ isDefault?: boolean;
10
+ };
@@ -0,0 +1,3 @@
1
+ import { TaskOrderType } from "./TaskOrderType";
2
+
3
+ export type TaskOrderMapType = Record<string, TaskOrderType[]>;
@@ -0,0 +1 @@
1
+ export type TaskOrderType = { id: number; parentId: number; order: number };
@@ -0,0 +1,3 @@
1
+ import { WorkItemOrderType } from "./WorkItemOrderType";
2
+
3
+ export type WorkItemOrderMapType = Record<string, WorkItemOrderType[]>;
@@ -0,0 +1 @@
1
+ export type WorkItemOrderType = { id: number; order: number };
@@ -0,0 +1 @@
1
+ export * from "./pluginCore.js";
@@ -0,0 +1,12 @@
1
+ export interface PluginConfig {
2
+ extensionId: string;
3
+ projectName?: string;
4
+ organizationUrl?: string;
5
+ }
6
+
7
+ export function createPluginContext(config: PluginConfig) {
8
+ return {
9
+ ...config,
10
+ createdAt: new Date().toISOString()
11
+ };
12
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "module": "ESNext",
7
+ "moduleResolution": "Node",
8
+ "noImplicitAny": false,
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "core/*": ["src/core/*"]
12
+ }
13
+ },
14
+ "include": ["src"],
15
+ "exclude": ["**/__tests__/**"]
16
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@danieli-automation/devops-plugin-features",
3
+ "version": "0.1.0",
4
+ "description": "Shared feature-level state, hooks, and helpers for Azure DevOps plugins",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "clean": "Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue",
21
+ "lint": "echo lint not configured",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "devDependencies": {
26
+ "vitest": "^3.2.4"
27
+ },
28
+ "publishConfig": {
29
+ "access": "restricted"
30
+ }
31
+ }
@@ -0,0 +1,12 @@
1
+ export type UIUser = {
2
+ id?: string;
3
+ descriptor?: string;
4
+ } | null;
5
+
6
+ /**
7
+ * Temporary package-level UI store hook.
8
+ * Consumers can replace or wrap this with application-specific state.
9
+ */
10
+ export function useUIStore(): { currentUser: UIUser } {
11
+ return { currentUser: null };
12
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Converts a date-like input to YYYY-MM-DD string.
3
+ */
4
+ export function toIsoDate(value: string | Date | null | undefined): string {
5
+ if (!value) return "";
6
+ const date = value instanceof Date ? value : new Date(value);
7
+ if (Number.isNaN(date.getTime())) return "";
8
+ return date.toISOString().slice(0, 10);
9
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Extracts the first path segment as project name.
3
+ */
4
+ export function extractProjectFromPath(pathValue: string | null | undefined): string {
5
+ if (!pathValue) return "";
6
+ const normalized = pathValue.replace(/\\/g, "/");
7
+ const [project] = normalized.split("/");
8
+ return project ?? "";
9
+ }
@@ -0,0 +1,12 @@
1
+ import { getClient } from "azure-devops-extension-api";
2
+ import { CoreRestClient } from "azure-devops-extension-api/Core";
3
+ import { GitRestClient } from "azure-devops-extension-api/Git";
4
+ import { GraphRestClient } from "azure-devops-extension-api/Graph";
5
+ import { WorkRestClient } from "azure-devops-extension-api/Work";
6
+ import { WorkItemTrackingRestClient } from "azure-devops-extension-api/WorkItemTracking";
7
+
8
+ export const coreClient = () => getClient(CoreRestClient);
9
+ export const witClient = () => getClient(WorkItemTrackingRestClient);
10
+ export const workClient = () => getClient(WorkRestClient);
11
+ export const gitClient = () => getClient(GitRestClient);
12
+ export const graphClient = () => getClient(GraphRestClient);
@@ -0,0 +1,5 @@
1
+ export default {
2
+ OWNERS_FIELD_MAX_LEN: 5,
3
+ TITLE_FIELD_MAX_LEN: 100,
4
+ DESCRIPTION_FIELD_MAX_LEN: 300,
5
+ }
@@ -0,0 +1,127 @@
1
+ import { useUIStore } from "app/stores/useUIStore";
2
+ import { GraphGroup } from "azure-devops-extension-api/Graph";
3
+ import { graphClient } from "core/azureClients";
4
+ import { useInstanceStore } from "features/instances/stores/useInstanceStore";
5
+ import * as React from "react";
6
+
7
+ type RoleState = {
8
+ isAdmin: boolean;
9
+ isJM: boolean;
10
+ groups: string[];
11
+ loading: boolean;
12
+ };
13
+
14
+ /**
15
+ * Centralized permission logic for instances.
16
+ * - canReorder = true -> user can drag & drop backlog order
17
+ * - canReorder = false -> instance is active and user is NOT a JM
18
+ */
19
+ export function useInstancePermission() {
20
+ const { currentInstance } = useInstanceStore();
21
+ const { currentUser } = useUIStore();
22
+ const isInstanceMode = !!currentInstance?.id;
23
+
24
+ const [roles, setRoles] = React.useState<RoleState>({
25
+ isAdmin: false,
26
+ isJM: false,
27
+ groups: [],
28
+ loading: false,
29
+ });
30
+
31
+ /*React.useEffect(() => {
32
+ if (!currentUser) return;
33
+
34
+ let mounted = true;
35
+
36
+ (async () => {
37
+ const result = await resolveUserRoles(currentUser);
38
+ if (mounted) setRoles({ ...result, loading: false });
39
+ })();
40
+
41
+ return () => {
42
+ mounted = false;
43
+ };
44
+ }, [currentUser]);*/
45
+
46
+ // Owner logic is **instance-specific**
47
+
48
+ const canEditInstance = React.useMemo(() => {
49
+ if (!isInstanceMode || !currentUser) return false;
50
+ const currentDescriptor = currentUser?.descriptor;
51
+ const createdByDescriptor = currentInstance.createdBy?.descriptor;
52
+ return currentInstance.createdBy.id === currentUser.id ||
53
+ (!!currentDescriptor && createdByDescriptor === currentDescriptor);
54
+ }, [isInstanceMode, currentInstance, currentUser]);
55
+
56
+ const canReorder = React.useMemo(() => {
57
+ if (!isInstanceMode) return true; // Personal Board Mode -> can reorder
58
+ const currentId = currentUser?.id;
59
+ const currentDescriptor = currentUser?.descriptor;
60
+ const isOwner =
61
+ currentInstance.createdBy.id === currentId ||
62
+ (!!currentDescriptor && currentInstance.createdBy?.descriptor === currentDescriptor);
63
+
64
+ const isInOwners =
65
+ (!!currentId && currentInstance.owners.includes(currentId)) ||
66
+ (!!currentDescriptor && currentInstance.owners.includes(currentDescriptor));
67
+
68
+ return isInOwners || isOwner;
69
+ }, [isInstanceMode, currentInstance, currentUser]);
70
+
71
+ return {
72
+ isInstanceMode,
73
+ canEditInstance, //roles.isAdmin || roles.isJM || isInstanceOwner,
74
+ canReorder,
75
+ currentUser,
76
+ loading: roles.loading,
77
+ groups: roles.groups,
78
+ /*
79
+ -----------------------------------------------------------------------------------------
80
+ @IMPORTANT: as SPM-HUB does not have public api and endpoint to verify the permissions,
81
+ we are going to bypass the permissions for now (meeting: 28.11.2025 - w/Luca)
82
+ -----------------------------------------------------------------------------------------
83
+ */
84
+ isAdmin: true,//roles.isAdmin,
85
+ isJM: true, // roles.isJM,
86
+ canCreateInstance: true, //roles.isAdmin || roles.isJM
87
+ };
88
+ }
89
+
90
+ //#region Private Functions
91
+ async function resolveUserRoles(user: any) {
92
+ try {
93
+ const descriptor = user.descriptor;
94
+ if (!descriptor) {
95
+ return { isAdmin: false, isJM: false, groups: [] as string[] };
96
+ }
97
+ const gClient = graphClient();
98
+ const memberships = await gClient.listMemberships(descriptor);
99
+
100
+ const roles = {
101
+ isAdmin: false,
102
+ isJM: false,
103
+ groups: [] as string[],
104
+ };
105
+
106
+ for (const m of memberships) {
107
+ const group = await gClient.getGroup(m.containerDescriptor) as GraphGroup;
108
+ const name = group?.displayName?.toLowerCase() ?? "";
109
+
110
+ roles.groups.push(name);
111
+
112
+ if (name.includes("project administrators") || name.includes("collection administrators")) {
113
+ roles.isAdmin = true;
114
+ }
115
+
116
+ if (name.includes("jm") || name.includes("owner")) {
117
+ roles.isJM = true;
118
+ }
119
+ }
120
+
121
+ return roles;
122
+ } catch (err) {
123
+ console.warn("resolveUserRoles error:", err);
124
+ return { isAdmin: false, isJM: false, groups: [] };
125
+ }
126
+ }
127
+ //#endregion
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { useInstanceStore } from "../useInstanceStore";
3
+
4
+ describe("useInstanceStore", () => {
5
+ //#region currentInstance
6
+ it("currentInstance: has correct initial state", () => {
7
+ expect(useInstanceStore.getState().currentInstance).toEqual(null);
8
+ });
9
+
10
+ it("setCurrentInstance: sets instance as current iteration.", () => {
11
+ const instance = {
12
+ id: "1",
13
+ name: "Instance Name",
14
+ org: "demo-org",
15
+ createdBy: { id: "u1" },
16
+ owners: [],
17
+ projectTeamPairs: [],
18
+ createdAt: "2025-01-01T00:00:00.000Z",
19
+ updatedAt: "2025-01-01T00:00:00.000Z"
20
+ };
21
+ useInstanceStore.getState().setCurrentInstance(instance);
22
+ expect(useInstanceStore.getState().currentInstance).toEqual(instance);
23
+ });
24
+ //#endregion
25
+ });
@@ -0,0 +1,7 @@
1
+ import { CrossSprintInstanceType } from "features/instances/types/CrossSprintInstanceType";
2
+
3
+ export type InstanceStoreType = {
4
+ currentInstance: CrossSprintInstanceType | null;
5
+
6
+ setCurrentInstance: (instance: CrossSprintInstanceType | null) => void;
7
+ };
@@ -0,0 +1,9 @@
1
+ import { CrossSprintInstanceType } from "features/instances/types/CrossSprintInstanceType";
2
+ import { InstanceStoreType } from "features/instances/stores/types/InstanceStoreType";
3
+ import { create } from "zustand";
4
+
5
+ export const useInstanceStore = create<InstanceStoreType>((set) => ({
6
+ currentInstance: null,
7
+
8
+ setCurrentInstance: (instance: CrossSprintInstanceType | null) => set({ currentInstance: instance }),
9
+ }));
@@ -0,0 +1,20 @@
1
+ import { SelectedProjectType } from "features/teams/types/SelectedProjectType";
2
+
3
+ export type CrossSprintInstanceType = {
4
+ id: string;
5
+ name: string;
6
+ description?: string;
7
+
8
+ org: string;
9
+ createdBy: any;
10
+ owners: string[];
11
+
12
+ projectTeamPairs: SelectedProjectType[];
13
+
14
+ createdAt: string;
15
+ updatedAt: string;
16
+
17
+ isDefault?: boolean;
18
+ };
19
+
20
+ export type CrossSprintInstanceMap = Record<string, CrossSprintInstanceType>;
@@ -0,0 +1,55 @@
1
+ import { CrossSprintInstanceType } from "features/instances/types/CrossSprintInstanceType";
2
+ import { TeamMember } from "features/teams/types/TeamMemberType";
3
+
4
+ /**
5
+ * Checks whether a user is owner-level for the instance (owner list or creator).
6
+ *
7
+ * @param inst Cross-sprint instance.
8
+ * @param user Current user.
9
+ * @returns `true` when user is owner or creator.
10
+ */
11
+ export function isInstanceOwner(inst: CrossSprintInstanceType, user: TeamMember) {
12
+ const uid = user?.id;
13
+ const descriptor = user?.descriptor;
14
+ if (!uid && !descriptor) return false;
15
+
16
+ const inOwners = (!!uid && inst.owners?.includes(uid)) || (!!descriptor && inst.owners?.includes(descriptor));
17
+
18
+ return inOwners || isInstanceCreator(inst, user);
19
+ }
20
+
21
+ /**
22
+ * Checks whether an instance is explicitly shared with a user but not created by them.
23
+ *
24
+ * @param inst Cross-sprint instance.
25
+ * @param user Current user.
26
+ * @returns `true` when user is in owners list and is not creator.
27
+ */
28
+ export function isInstanceSharedWithMe(inst: CrossSprintInstanceType, user: TeamMember) {
29
+ const uid = user?.id;
30
+ const descriptor = user?.descriptor;
31
+ if (!uid && !descriptor) return false;
32
+
33
+ const inOwners =
34
+ (!!uid && inst.owners?.includes(uid)) ||
35
+ (!!descriptor && inst.owners?.includes(descriptor));
36
+
37
+ return inOwners && !isInstanceCreator(inst, user);
38
+ }
39
+
40
+ /**
41
+ * Checks whether a user is the creator of the given instance.
42
+ *
43
+ * @param inst Cross-sprint instance.
44
+ * @param user Current user.
45
+ * @returns `true` when created-by identity matches user id or descriptor.
46
+ */
47
+ export function isInstanceCreator(inst: CrossSprintInstanceType, user: TeamMember) {
48
+ const uid = user?.id;
49
+ const descriptor = user?.descriptor;
50
+
51
+ return (
52
+ (!!uid && inst.createdBy?.id === uid) ||
53
+ (!!descriptor && inst.createdBy?.descriptor === descriptor)
54
+ );
55
+ }