Skip to content

Commit

Permalink
Merge pull request #237 from agiledigital/feature/IE-3-port-jiralint-…
Browse files Browse the repository at this point in the history
…to-jira-cloud

Feature/ie 3 port jiralint to jira cloud
  • Loading branch information
dspasojevic authored Jan 27, 2023
2 parents 911458c + 244a05a commit 245ce87
Show file tree
Hide file tree
Showing 22 changed files with 757 additions and 296 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,7 @@ dist

# direnv config file
.envrc

# personalised usage
.npmrc
.jiralintrc
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ you can now run jiralint search like this:
cli/dist/jiralint search -j "project=MF order by created"
```

#### Example OAuth `.jirilintrc`
#### Example OAuth `.jiralintrc`

```json
{
Expand All @@ -138,7 +138,7 @@ cli/dist/jiralint search -j "project=MF order by created"
}
```

#### Example Personal Access Token `.jirilintrc`
#### Example Personal Access Token `.jiralintrc`

```json
{
Expand All @@ -149,7 +149,21 @@ cli/dist/jiralint search -j "project=MF order by created"
}
```

#### Example User Credentials `.jirilintrc`
#### Example User Credentials `.jiralintrc`

Note that Cloud Jira access using usernames and passwords is now deprecated. Furthermore, a specific quality and quality reason field has been added for Jiralint usage. When trying to access Jira Cloud using user credentials, use the following:

```json
{
"username": "[username]:",
"password": "[API token]",
"jiraHost": "agiledigital.atlassian.net",
"qualityFieldName": "customfield_10147",
"qualityReasonFieldName": "customfield_10148"
}
```

You can create a Jira API token for your account [here](https://id.atlassian.com/manage-profile/security/api-tokens). Note that the colon after your login email is neccesary. Below is how you can sign in using a standard username and password via OnPrem

```json
{
Expand Down
8 changes: 5 additions & 3 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
import { pipe, flow } from "fp-ts/lib/function";
import {
JiraClient,
Expand Down Expand Up @@ -134,7 +135,7 @@ type JiraParameters = {
};

type JiraClientBuilder = JiraParameters & {
readonly missingParameters: ReadonlyArray<PropertyKey>;
readonly missingParameters: readonly PropertyKey[];
readonly client?: JiraClient;
};

Expand Down Expand Up @@ -162,7 +163,7 @@ const hasMandatoryParameter =
*/
const addMissingParameters = (
builder: JiraClientBuilder,
parameters: ReadonlyArray<keyof JiraParameters>
parameters: readonly (keyof JiraParameters)[]
): JiraClientBuilder => ({
...builder,
missingParameters: [
Expand Down Expand Up @@ -266,7 +267,7 @@ const makeUserCredentialsClient = (
}
: builder;

const jiraParamList = (params: ReadonlyArray<PropertyKey>): string =>
const jiraParamList = (params: readonly PropertyKey[]): string =>
params.map((p) => ` jira.${String(p)}`).join("\n");

/**
Expand Down Expand Up @@ -314,6 +315,7 @@ export const withCommonOptions = <C extends RootCommand>(command: C) =>
alias: "ck",
type: "string",
describe: "The Jira consumer key",
// eslint-disable-next-line spellcheck/spell-checker
default: config?.jiraConsumerKey ?? "jiralintkey", // TODO remove this default?
})
.option(jiraConsumerSecretOptionKey, {
Expand Down
3 changes: 3 additions & 0 deletions cli/src/scripts/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,14 @@ const auth = async (
: console.info(JSON.stringify(user.right, null, 2));
};

// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
export default ({ command }: RootCommand): Argv<unknown> =>
command(
"auth",
// eslint-disable-next-line spellcheck/spell-checker
"authorises the linter to call Jira APIs and outputs the access token and secret",
(yargs) => withAuthenticationOptions(yargs),
// eslint-disable-next-line functional/no-return-void
(args) => {
const protocol = args["jira.protocol"];
// eslint-disable-next-line functional/no-expression-statement
Expand Down
4 changes: 2 additions & 2 deletions cli/src/scripts/rate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ export default ({ command }: RootCommand): Argv<unknown> =>
(yargs) =>
withQualityFieldsOption(yargs)
.option("key", {
alias: "k",
type: "string",
describe: "issue key",
})
.option("quality", {
type: "string",
alias: "q",
choices: ["A", "B"],
description: "assessed quality",
})
.option("reason", {
Expand All @@ -44,6 +43,7 @@ export default ({ command }: RootCommand): Argv<unknown> =>
describe: "reason for assessment",
})
.demandOption(["key", "quality", "reason"]),
// eslint-disable-next-line functional/no-return-void
(args) => {
// eslint-disable-next-line functional/no-expression-statement
void rate(
Expand Down
51 changes: 29 additions & 22 deletions cli/src/scripts/search.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { JiraClient } from "../../../lib/src/services/jira_api";
import { Argv } from "yargs";
import { RootCommand, withQualityFieldsOption } from "..";
import { EnhancedIssue, quality } from "@agiledigital/jiralint-lib";
import { issueActionRequired, IssueAction } from "@agiledigital/jiralint-lib";
import { isLeft } from "fp-ts/lib/Either";
import { readonlyDate } from "readonly-types/dist";
import {
EnhancedIssue,
quality,
issueActionRequired,
IssueAction,
jiraFormattedDistance,
jiraFormattedSeconds,
} from "@agiledigital/jiralint-lib";
import { isLeft } from "fp-ts/lib/Either";
import { readonlyDate } from "readonly-types/dist";
import stringLength from "string-length";

import * as CLUI from "clui";
Expand All @@ -17,15 +19,15 @@ import * as clc from "cli-color";
// eslint-disable-next-line functional/no-expression-statement
require("cli-color");

type CheckedIssue = EnhancedIssue & {
readonly action: IssueAction;
readonly reasons: readonly string[];
readonly issueQuality: string;
};

const checkedIssues = (
issues: ReadonlyArray<EnhancedIssue>
): ReadonlyArray<
EnhancedIssue & {
readonly action: IssueAction;
readonly reasons: ReadonlyArray<string>;
readonly issueQuality: string;
}
> => {
issues: readonly EnhancedIssue[]
): readonly CheckedIssue[] => {
// eslint-disable-next-line no-restricted-globals
const now = readonlyDate(new Date());
return issues.map((issue) => {
Expand All @@ -47,19 +49,22 @@ const checkedIssues = (
};

// eslint-disable-next-line functional/no-return-void
const renderJson = (issues: ReadonlyArray<EnhancedIssue>): void => {
// eslint-disable-next-line functional/no-expression-statement
const renderJson = (issues: readonly EnhancedIssue[]): void => {
// eslint-disable-next-line functional/no-expression-statement, functional/no-return-void
checkedIssues(issues).forEach((issue) =>
console.log(JSON.stringify(issue, null, 2))
);
};

const renderTable = (
issues: ReadonlyArray<EnhancedIssue>,
issues: readonly EnhancedIssue[],
qualityFieldName: string
// eslint-disable-next-line functional/no-return-void
): void => {
const tableHeaders: ReadonlyArray<string> = [
// eslint-disable-next-line functional/no-expression-statement
console.clear();

const tableHeaders: readonly string[] = [
"Action",
"Quality",
"Key",
Expand All @@ -79,7 +84,7 @@ const renderTable = (
// unicode characters.
const alarm = ["⠀", "⠁", "⠉", "⠋", "⠛", "⣿"] as const;

const tableHeaderWidths: ReadonlyArray<number> = tableHeaders.map(
const tableHeaderWidths: readonly number[] = tableHeaders.map(
(header) => stringLength(header) + 1
);

Expand All @@ -93,9 +98,10 @@ const renderTable = (
// eslint-disable-next-line no-restricted-globals
const now = readonlyDate(new Date());

const data: ReadonlyArray<
ReadonlyArray<readonly [string, ReadonlyArray<clc.Format>]>
> = checkedIssues(issues).map((issue) => {
const data: readonly (readonly (readonly [
string,
readonly clc.Format[]
])[])[] = checkedIssues(issues).map((issue) => {
const originalEstimateSeconds =
issue.fields.timetracking.originalEstimateSeconds ?? 0;
const timeSpentSeconds = issue.fields.timetracking.timeSpentSeconds ?? 0;
Expand All @@ -115,7 +121,7 @@ const renderTable = (
? jiraFormattedDistance(now, issue.mostRecentTransition.created)
: "";

const noFormat: ReadonlyArray<clc.Format> = [clc.white];
const noFormat: readonly clc.Format[] = [clc.white];
const quality = issue.fields[qualityFieldName];

return [
Expand Down Expand Up @@ -152,7 +158,7 @@ const renderTable = (
}, tableHeaderWidths);

const renderRow = (
row: ReadonlyArray<readonly [string, ReadonlyArray<clc.Format>]>
row: readonly (readonly [string, readonly clc.Format[]])[]
// eslint-disable-next-line functional/no-return-void
): void => {
const columns = row.reduce((line, [text], index) => {
Expand Down Expand Up @@ -242,6 +248,7 @@ export default ({ command }: RootCommand): Argv<unknown> =>
default: [],
})
.demandOption(["jql"]),
// eslint-disable-next-line functional/no-return-void
(args) => {
// eslint-disable-next-line functional/no-expression-statement
void search(
Expand Down
6 changes: 6 additions & 0 deletions lib/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ module.exports = {
"clc",
"codec",
"Codec",
"duedate",
"danixon",
"fc",
"fieldtype",
"globals",
"io",
"issuetype",
Expand All @@ -48,6 +52,7 @@ module.exports = {
"proxied",
"Readonly",
"readonly",
"rollup",
"servlet",
"sonarjs",
"subtask",
Expand All @@ -57,6 +62,7 @@ module.exports = {
"utf8",
"Urls",
"versioned",
"viewlink",
"worklog",
"Worklog",
"worklogs",
Expand Down
2 changes: 1 addition & 1 deletion lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
"typescript": "4.8.2"
},
"typeCoverage": {
"atLeast": 100,
"atLeast": 99,
"ignoreCatch": false,
"strict": true,
"detail": true
Expand Down
1 change: 1 addition & 0 deletions lib/src/codecs/null_or_missing.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable functional/no-return-void */
/* eslint-disable functional/functional-parameters */
/* eslint-disable functional/no-expression-statement */
/* eslint-disable functional/no-throw-statement */
Expand Down
2 changes: 1 addition & 1 deletion lib/src/codecs/null_or_missing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ import * as ITT from "io-ts-types";
export const nullOrMissingToUndefined = <P, O = P>(
t: T.Type<P, O>
// eslint-disable-next-line functional/prefer-readonly-type
): T.UnionC<[T.Type<P, O, unknown>, T.UndefinedC]> =>
): T.UnionC<[T.Type<P, O>, T.UndefinedC]> =>
ITT.fromNullable(T.union([t, T.undefined]), undefined);
1 change: 1 addition & 0 deletions lib/src/codecs/readonly_date.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable functional/no-return-void */
/* eslint-disable functional/functional-parameters */
/* eslint-disable functional/no-expression-statement */
/* eslint-disable functional/no-throw-statement */
Expand Down
21 changes: 19 additions & 2 deletions lib/src/codecs/readonly_date.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as T from "io-ts";
import * as ITT from "io-ts-types";
import * as E from "fp-ts/Either";
import { readonlyDate, ReadonlyDate } from "readonly-types/dist";

/* eslint-disable no-restricted-globals, @typescript-eslint/ban-types */
/* eslint-disable no-restricted-globals, @typescript-eslint/ban-types*/
/**
* Codec to convert between a `Date` and a `ReadonlyDate`.
*/
Expand All @@ -12,7 +13,6 @@ export const readonlyDateFromDate = new T.Type<ReadonlyDate, Date, Date>(
(u) => T.success(readonlyDate(u)),
(a) => new Date(a.valueOf())
);
/* eslint-enable no-restricted-globals, @typescript-eslint/ban-types */

/**
* Codec to convert between a ISO string representation of a date and a readonly date.
Expand All @@ -21,3 +21,20 @@ export const readOnlyDateFromISOString = ITT.DateFromISOString.pipe(
readonlyDateFromDate,
"ReadonlyDate from ISO string"
);

export const ReadonlyDateC = new T.Type<ReadonlyDate, Date, Date>(
"ReadonlyDate",
ITT.date.is,
(u: Date) => E.of(readonlyDate(u)),
(a) => new Date(a.valueOf())
);

/**
* Identity codec to convert between a readonly date and a readonly date. Will work if input is either date or readonly * date.
*/
export const dateToDate = ITT.date.pipe(
ReadonlyDateC,
"ReadonlyDate from Date"
);

/* eslint-enable no-restricted-globals, @typescript-eslint/ban-types*/
1 change: 1 addition & 0 deletions lib/src/services/issue_checks.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable spellcheck/spell-checker */
/* eslint-disable functional/no-return-void */
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable no-restricted-globals */
Expand Down
5 changes: 3 additions & 2 deletions lib/src/services/issue_checks.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable spellcheck/spell-checker */
/* eslint-disable functional/functional-parameters */
import { EnhancedIssue as EnhancedIssue } from "./jira";
import { NonEmptyArray } from "fp-ts/lib/NonEmptyArray";
Expand Down Expand Up @@ -25,7 +26,7 @@ export type Action = "none" | "inspect";

export type IssueAction = {
readonly actionRequired: Action;
readonly checks: ReadonlyArray<CheckResult>;
readonly checks: readonly CheckResult[];
};

export type Checker = {
Expand Down Expand Up @@ -288,7 +289,7 @@ export const validateComment =

const check = (
issue: EnhancedIssue,
checks: ReadonlyArray<(t: EnhancedIssue) => CheckResult>
checks: readonly ((t: EnhancedIssue) => CheckResult)[]
): IssueAction => {
const noAction: IssueAction = {
actionRequired: "none",
Expand Down
1 change: 1 addition & 0 deletions lib/src/services/issue_quality.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable functional/no-return-void */
/* eslint-disable functional/functional-parameters */
/* eslint-disable functional/no-expression-statement */
import { IssueAction } from "./issue_checks";
Expand Down
1 change: 1 addition & 0 deletions lib/src/services/issue_quality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IssueAction } from "./issue_checks";
* @param issueAction the actionable info about the issue.
* @returns the overall quality.
*/

export const quality = (issueAction: IssueAction): string => {
const numberOfFailures = issueAction.checks.filter(
(check) => check.outcome === "fail"
Expand Down
Loading

0 comments on commit 245ce87

Please sign in to comment.