Skip to content

Commit

Permalink
Merge pull request #182 from bitovi/TR-78-Global-Teams-Configuration
Browse files Browse the repository at this point in the history
Tr 78 global teams configuration
  • Loading branch information
binaryberserker authored Oct 10, 2024
2 parents 73ba9c8 + 016b540 commit 8a46667
Show file tree
Hide file tree
Showing 49 changed files with 664 additions and 492 deletions.
11 changes: 11 additions & 0 deletions public/css/steerco-reporting.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@
--fullish-document-top: calc(40px + 20px);
}

@layer utilities {
.no-spin-container > input[type="number"]::-webkit-outer-spin-button,
.no-spin-container > input[type="number"]::-webkit-inner-spin-button {
@apply appearance-none;
}

.no-spin-container > input[type="number"] {
@apply appearance-none;
-moz-appearance: textfield; /* firefox */
}
}

@layer components {
.form-border {
Expand Down
78 changes: 23 additions & 55 deletions public/jira/normalized/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ type ChildField<F extends keyof JiraIssue["fields"]> = {
type Fields = Pick<JiraIssue, "fields">;
type Key = Pick<JiraIssue, "key">;

export function getSummaryDefault({
fields,
}: ParentField<"summary"> | ChildField<"Summary">): string {
export function getSummaryDefault({ fields }: ParentField<"summary"> | ChildField<"Summary">): string {
if ("summary" in fields) {
return fields.summary;
}
Expand All @@ -29,35 +27,26 @@ export function getStartDateDefault({ fields }: Fields): string | null {
return fields["Start date"] || null;
}

export function getStoryPointsDefault({
fields,
}: Fields): NormalizedIssue["storyPoints"] {
export function getStoryPointsDefault({ fields }: Fields): NormalizedIssue["storyPoints"] {
return fields["Story points"] || null;
}

export function getStoryPointsMedianDefault({
fields,
}: Fields): NormalizedIssue["storyPointsMedian"] {
export function getStoryPointsMedianDefault({ fields }: Fields): NormalizedIssue["storyPointsMedian"] {
return fields["Story points median"] || null;
}

export function getRankDefault({ fields }: Fields): NormalizedIssue["rank"] {
return fields?.Rank || null;
}

export function getConfidenceDefault({
fields,
}: Fields): NormalizedIssue["confidence"] {
export function getConfidenceDefault({ fields }: Fields): NormalizedIssue["confidence"] {
return fields["Story points confidence"] || fields?.Confidence || null;
}

export function getHierarchyLevelDefault({
fields,
}:
| ChildField<"Issue Type">
| ParentField<"issuetype">): NormalizedIssue["hierarchyLevel"] {
const issueType =
"Issue Type" in fields ? fields["Issue Type"] : fields.issuetype;
}: ChildField<"Issue Type"> | ParentField<"issuetype">): NormalizedIssue["hierarchyLevel"] {
const issueType = "Issue Type" in fields ? fields["Issue Type"] : fields.issuetype;

if (typeof issueType === "string") {
return parseInt(issueType, 10);
Expand All @@ -70,9 +59,7 @@ export function getIssueKeyDefault({ key }: Key): NormalizedIssue["key"] {
return key;
}

export function getParentKeyDefault({
fields,
}: Fields): NormalizedIssue["parentKey"] {
export function getParentKeyDefault({ fields }: Fields): NormalizedIssue["parentKey"] {
if (fields?.Parent?.key) {
return fields.Parent.key;
}
Expand All @@ -85,25 +72,18 @@ export function getParentKeyDefault({
return fields["Parent Link"]?.data?.key || null;
}

export function getUrlDefault({
key,
}: Pick<JiraIssue, "key">): NormalizedIssue["url"] {
export function getUrlDefault({ key }: Pick<JiraIssue, "key">): NormalizedIssue["url"] {
return "javascript://";
}

export function getTeamKeyDefault({
key,
}: Pick<JiraIssue, "key">): NormalizedIssue["team"]["name"] {
export function getTeamKeyDefault({ key }: Pick<JiraIssue, "key">): NormalizedIssue["team"]["name"] {
return key.replace(/-.*/, "");
}

export function getTypeDefault({
fields,
}:
| ChildField<"Issue Type">
| ParentField<"issuetype">): NormalizedIssue["type"] {
const issueType =
"Issue Type" in fields ? fields["Issue Type"] : fields.issuetype;
}: ChildField<"Issue Type"> | ParentField<"issuetype">): NormalizedIssue["type"] {
const issueType = "Issue Type" in fields ? fields["Issue Type"] : fields.issuetype;

if (typeof issueType === "string") {
return issueType;
Expand All @@ -112,9 +92,7 @@ export function getTypeDefault({
return issueType.name;
}

export function getSprintsDefault({
fields,
}: Fields): NormalizedIssue["sprints"] {
export function getSprintsDefault({ fields }: Fields): NormalizedIssue["sprints"] {
if (!fields.Sprint) {
return null;
}
Expand All @@ -127,35 +105,27 @@ export function getSprintsDefault({
});
}

export function getStatusDefault({
fields,
}: Fields): NormalizedIssue["status"] {
export function getStatusDefault({ fields }: Fields): NormalizedIssue["status"] {
if (typeof fields?.Status === "string") {
return fields.Status;
}

return fields?.Status?.name || null;
}

export function getLabelsDefault({
fields,
}: Fields): NormalizedIssue["labels"] {
export function getLabelsDefault({ fields }: Fields): NormalizedIssue["labels"] {
return fields?.Labels || [];
}

export function getStatusCategoryDefault({
fields,
}: Fields): NormalizedIssue["statusCategory"] {
export function getStatusCategoryDefault({ fields }: Fields): NormalizedIssue["statusCategory"] {
if (typeof fields?.Status === "string") {
return null;
}

return fields?.Status?.statusCategory?.name || null;
}

export function getReleasesDefault({
fields,
}: Fields): NormalizedIssue["releases"] {
export function getReleasesDefault({ fields }: Fields): NormalizedIssue["releases"] {
let fixVersions = fields["Fix versions"];

if (!fixVersions) {
Expand All @@ -176,20 +146,18 @@ export function getReleasesDefault({
});
}

export function getVelocityDefault(
teamKey: string
): NormalizedIssue["team"]["velocity"] {
export function getVelocityDefault(teamKey: string): NormalizedIssue["team"]["velocity"] {
return 21;
}

export function getParallelWorkLimitDefault(
teamKey: string
): NormalizedIssue["team"]["parallelWorkLimit"] {
export function getParallelWorkLimitDefault(teamKey: string): NormalizedIssue["team"]["parallelWorkLimit"] {
return 1;
}

export function getDaysPerSprintDefault(
teamKey: string
): NormalizedIssue["team"]["daysPerSprint"] {
export function getDaysPerSprintDefault(teamKey: string): NormalizedIssue["team"]["daysPerSprint"] {
return 10;
}

export function getTeamSpreadsEffortAcrossDatesDefault(): NormalizedIssue["team"]["spreadEffortAcrossDates"] {
return false;
}
9 changes: 4 additions & 5 deletions public/jira/normalized/normalize.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { expect, test } from "vitest";

import {
NormalizeIssueConfig,
normalizeIssue,
normalizeParent,
} from "./normalize";
import { NormalizeIssueConfig, normalizeIssue, normalizeParent } from "./normalize";
import { parseDateIntoLocalTimezone } from "../../date-helpers";
import { JiraIssue, ParentIssue } from "../shared/types";

Expand Down Expand Up @@ -97,6 +93,7 @@ test("normalizeIssue", () => {
parallelWorkLimit: 1,
totalPointsPerDay: 2.1,
pointsPerDayPerTrack: 2.1,
spreadEffortAcrossDates: false,
},
url: "javascript://",
status: "Done",
Expand Down Expand Up @@ -130,6 +127,7 @@ test("normalizeIssue with custom getters", () => {
};

const overrides: NormalizeIssueConfig = {
getTeamSpreadsEffortAcrossDates: () => false,
getSummary: () => {
return "summary";
},
Expand Down Expand Up @@ -246,6 +244,7 @@ test("normalizeIssue with custom getters", () => {
parallelWorkLimit: 1,
totalPointsPerDay: 0.05,
pointsPerDayPerTrack: 0.05,
spreadEffortAcrossDates: false,
},
url: "fake url",
status: "nice",
Expand Down
11 changes: 4 additions & 7 deletions public/jira/normalized/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ type DefaultsToConfig<T> = {

export type NormalizeIssueConfig = DefaultsToConfig<typeof defaults>;
export type NormalizeParentConfig = DefaultsToConfig<
Pick<
typeof defaults,
"getSummaryDefault" | "getHierarchyLevelDefault" | "getTypeDefault"
>
Pick<typeof defaults, "getSummaryDefault" | "getHierarchyLevelDefault" | "getTypeDefault">
>;

export function normalizeParent(
Expand Down Expand Up @@ -54,6 +51,7 @@ export function normalizeIssue(
getReleases = defaults.getReleasesDefault,
getRank = defaults.getRankDefault,
getSummary = defaults.getSummaryDefault,
getTeamSpreadsEffortAcrossDates = defaults.getTeamSpreadsEffortAcrossDatesDefault,
}: Partial<NormalizeIssueConfig> = {}
): NormalizedIssue {
const teamName = getTeamKey(issue);
Expand Down Expand Up @@ -86,6 +84,7 @@ export function normalizeIssue(
parallelWorkLimit,
totalPointsPerDay,
pointsPerDayPerTrack,
spreadEffortAcrossDates: getTeamSpreadsEffortAcrossDates(),
},
url: getUrl(issue),
status: getStatus(issue),
Expand All @@ -104,9 +103,7 @@ export function allStatusesSorted(issues: { status: string }[]): string[] {
}

export function allReleasesSorted(issues: NormalizedIssue[]): string[] {
const releases = issues
.map((issue) => issue.releases.map((r) => r.name))
.flat(1);
const releases = issues.map((issue) => issue.releases.map((r) => r.name)).flat(1);

return [...new Set(releases)].sort();
}
1 change: 1 addition & 0 deletions public/jira/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export interface NormalizedTeam {
parallelWorkLimit: number;
totalPointsPerDay: number;
pointsPerDayPerTrack: number;
spreadEffortAcrossDates: boolean;
}

export type DefaultsToConfig<T> = {
Expand Down
4 changes: 1 addition & 3 deletions public/jira/storage/common.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import jiraHelpers from "../../jira-oidc-helpers";

export type StorageFactory = (jiraHelper: ReturnType<typeof jiraHelpers>) => {
get: <TData>(key: string) => Promise<TData>;
get: <TData>(key: string) => Promise<TData | null>;
update: <TData>(key: string, value: TData) => Promise<void>;
storageContainerExists: (key: string) => Promise<Boolean>;
createStorageContainer: <TData>(key: string, value: TData) => Promise<void>;
};

export type AppStorage = ReturnType<StorageFactory>;
38 changes: 18 additions & 20 deletions public/jira/storage/index.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,33 +40,31 @@ const createUpdate = (jiraHelpers: Parameters<StorageFactory>[number]) => {

export const createJiraPluginStorage: StorageFactory = (jiraHelpers) => {
return {
storageContainerExists: async function (key) {
get: async function <TData>(key: string): Promise<TData | null> {
if (!AP) {
throw new Error("[Storage Error]: canUseStorage (plugin) can only be used when connected with jira.");
throw new Error("[Storage Error]: get (plugin) can only be used when connected with jira.");
}

return AP.request<{ body: string }>(`/rest/atlassian-connect/1/addons/${jiraHelpers.appKey}/properties`).then(
(res) => {
const parsed = JSON.parse(res.body) as { keys: Array<{ key: string; self: string }> };
return AP.request<{ body: string }>(`/rest/atlassian-connect/1/addons/${jiraHelpers.appKey}/properties/${key}`)
.then((res) => {
const parsed = JSON.parse(res.body) as AppPropertyResponse<TData>;

return !!parsed.keys.find((keyData) => keyData.key === key);
}
);
},
get: async function <TData>(key: string): Promise<TData> {
if (!AP) {
throw new Error("[Storage Error]: get (plugin) can only be used when connected with jira.");
}
return parsed.value;
})
.catch((error) => {
if ("err" in error) {
const parsed = JSON.parse(error.err) as { statusCode: number; message: string };

return AP.request<{ body: string }>(
`/rest/atlassian-connect/1/addons/${jiraHelpers.appKey}/properties/${key}`
).then((res) => {
const parsed = JSON.parse(res.body) as AppPropertyResponse<TData>;
if (parsed.statusCode === 404) {
const createContainer = createUpdate(jiraHelpers);
const newValue = null;

return parsed.value;
});
return createContainer(key, newValue).then(() => newValue);
}
}
throw error;
});
},
update: createUpdate(jiraHelpers),
createStorageContainer: createUpdate(jiraHelpers),
};
};
13 changes: 3 additions & 10 deletions public/jira/storage/index.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,17 @@ const createCodeBlock = (using?: string): CodeBlock => {

export const createWebAppStorage: StorageFactory = (jiraHelpers) => {
return {
storageContainerExists: async function () {
const configurationIssue = await getConfigurationIssue(jiraHelpers);
return !!configurationIssue;
},
get: async function <TData>(key: string): Promise<TData> {
get: async function <TData>(key: string): Promise<TData | null> {
const configurationIssue = await getConfigurationIssue(jiraHelpers);

if (!configurationIssue) {
throw new Error("[Storage Error]: get (web-app) needs a configuration issue");
return null;
}

let storeContent = configurationIssue.fields.Description.content.find((content) => content.type === "codeBlock");

if (!storeContent) {
storeContent = createCodeBlock();
storeContent = createCodeBlock(JSON.stringify({ [key]: {} }));
}

const [stringifiedStore] = storeContent.content;
Expand Down Expand Up @@ -98,8 +94,5 @@ export const createWebAppStorage: StorageFactory = (jiraHelpers) => {
},
});
},
createStorageContainer: async function <TData>(key: string, value: TData) {
window.location.href = "https://github.com/bitovi/jira-auto-scheduler/blob/main/docs/saved-configuration.md";
},
};
};
Loading

0 comments on commit 8a46667

Please sign in to comment.