Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Jira issues to the EmberData store #652

Merged
merged 3 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions web/app/adapters/jira-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import DS from "ember-data";
import ApplicationAdapter from "./application";
import RSVP from "rsvp";
import ModelRegistry from "ember-data/types/registries/model";
import JiraIssueModel from "hermes/models/jira-issue";

export default class JiraIssueAdapter extends ApplicationAdapter {
findRecord<K extends string | number>(
_store: DS.Store,
_type: ModelRegistry[K],
id: string,
_snapshot: DS.Snapshot<K>,
): RSVP.Promise<JiraIssueModel> {
const issue = this.fetchSvc
.fetch(`/api/${this.configSvc.config.api_version}/jira/issues/${id}`)
.then((response) => response?.json());

return RSVP.resolve(issue);
}
}
7 changes: 4 additions & 3 deletions web/app/components/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Resize } from "ember-animated/motions/resize";
import { easeOutExpo, easeOutQuad } from "hermes/utils/ember-animated/easings";
import animateTransform from "hermes/utils/ember-animated/animate-transform";
import RouterService from "@ember/routing/router-service";
import StoreService from "hermes/services/store";

const animationDuration = Ember.testing ? 0 : 450;

Expand Down Expand Up @@ -58,6 +59,7 @@ export default class ProjectIndexComponent extends Component<ProjectIndexCompone
@service("config") declare configSvc: ConfigService;
@service declare flashMessages: HermesFlashMessagesService;
@service declare router: RouterService;
@service declare store: StoreService;

/**
* The array of possible project statuses.
Expand Down Expand Up @@ -602,9 +604,8 @@ export default class ProjectIndexComponent extends Component<ProjectIndexCompone
*/
loadJiraIssue = task(async (jiraIssueID?: string) => {
const id = jiraIssueID ?? this.args.project.jiraIssueID;
const issue = await this.fetchSvc
.fetch(`/api/${this.configSvc.config.api_version}/jira/issues/${id}`)
.then((response) => response?.json());
assert("jiraIssueID must exist", id);
const issue = await this.store.findRecord("jira-issue", id);
this.jiraIssue = issue;
});

Expand Down
5 changes: 3 additions & 2 deletions web/app/components/project/jira-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import { tracked } from "@glimmer/tracking";
import { inject as service } from "@ember/service";
import { restartableTask } from "ember-concurrency";
import FetchService from "hermes/services/fetch";
import { JiraIssue, JiraPickerResult } from "hermes/types/project";
import { JiraPickerResult } from "hermes/types/project";
import ConfigService from "hermes/services/config";
import { XDropdownListAnchorAPI } from "../x/dropdown-list";
import { next } from "@ember/runloop";
import { assert } from "@ember/debug";
import JiraIssueModel from "hermes/models/jira-issue";

interface ProjectJiraWidgetComponentSignature {
Element: HTMLDivElement;
Args: {
issue?: JiraPickerResult | JiraIssue;
issue?: JiraPickerResult | JiraIssueModel;
onIssueSelect?: (issue: any) => void;
onIssueRemove?: () => void;
isDisabled?: boolean;
Expand Down
3 changes: 1 addition & 2 deletions web/app/components/project/tile.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
<ExternalLink
data-test-jira-link
href={{this.jiraIssue.url}}
class="jira inline-flex items-center gap-1.5
{{if this.jiraIssue 'fade-in-forwards'}}"
class="jira inline-flex items-center gap-1.5"
tabindex="-1"
>
<div class="flex h-4 w-4">
Expand Down
25 changes: 13 additions & 12 deletions web/app/components/project/tile.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { assert } from "@ember/debug";
import { inject as service } from "@ember/service";
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { task } from "ember-concurrency";
import JiraIssueModel from "hermes/models/jira-issue";
import ConfigService from "hermes/services/config";
import FetchService from "hermes/services/fetch";
import {
HermesProject,
HermesProjectHit,
JiraIssue,
} from "hermes/types/project";
import StoreService from "hermes/services/store";
import { HermesProject, HermesProjectHit } from "hermes/types/project";

export const PROJECT_TILE_MAX_PRODUCTS = 3;

Expand All @@ -27,6 +26,7 @@ interface ProjectTileComponentSignature {
export default class ProjectTileComponent extends Component<ProjectTileComponentSignature> {
@service("fetch") declare fetchSvc: FetchService;
@service("config") declare configSvc: ConfigService;
@service declare store: StoreService;

constructor(owner: unknown, args: ProjectTileComponentSignature["Args"]) {
super(owner, args);
Expand All @@ -47,7 +47,7 @@ export default class ProjectTileComponent extends Component<ProjectTileComponent
* Used in the template to determine whether to show Jira-related data.
* Set by the `fetchJiraIssue` task if the project has a jiraIssueID.
*/
@tracked protected jiraIssue: JiraIssue | null = null;
@tracked protected jiraIssue: JiraIssueModel | null = null;

/**
* The project ID used as our LinkTo model.
Expand Down Expand Up @@ -96,13 +96,14 @@ export default class ProjectTileComponent extends Component<ProjectTileComponent
* Called in the constructor if the project has a jiraIssueID.
*/
protected fetchJiraIssue = task(async () => {
const jiraIssue = await this.fetchSvc
.fetch(
`/api/${this.configSvc.config.api_version}/jira/issues/${this.args.project.jiraIssueID}`,
)
.then((resp) => resp?.json());
assert("jiraIssueID must exist", this.args.project.jiraIssueID);

this.jiraIssue = jiraIssue as JiraIssue;
const jiraIssue = await this.store.findRecord(
"jira-issue",
this.args.project.jiraIssueID,
);

this.jiraIssue = jiraIssue;
});
}

Expand Down
51 changes: 51 additions & 0 deletions web/app/models/jira-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Model, { attr } from "@ember-data/model";

export default class JiraIssueModel extends Model {
/**
* The Jira issue key, e.g., "PROJ-123".
* Used as the ID for the JiraIssue model.
*/
@attr declare key: string;

/**
* The summary of the issue.
*/
@attr declare summary?: string;

/**
* The issue permalink.
*/
@attr declare url: string;

/**
* The status, e.g., "Open".
*/
@attr declare status: string;

/**
* The assignee of the issue.
*/
@attr declare assignee?: string;

/**
* The type of issue, e.g., "Bug".
*/
@attr declare issueType?: string;

/**
* The URL to the issue type image.
* TODO: Host these ourselves to avoid broken images.
*/
@attr declare issueTypeImage: string;

/**
* The issue priority, e.g., "High".
*/
@attr declare priority?: string;

/**
* The URL to the priority image.
* TODO: Host these ourselves to avoid broken images.
*/
@attr declare priorityImage: string;
}
25 changes: 25 additions & 0 deletions web/app/serializers/jira-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import JSONSerializer from "@ember-data/serializer/json";
import DS from "ember-data";
import JiraIssueModel from "hermes/models/jira-issue";

export default class JiraIssueSerializer extends JSONSerializer {
/**
* The serializer for the JiraIssue model.
* Formats responses to the JSON spec.
*/
normalizeResponse(
_store: DS.Store,
_primaryModelClass: any,
payload: JiraIssueModel,
_id: string | number,
_requestType: string,
) {
return {
data: {
id: payload.key,
type: "jira-issue",
attributes: payload,
},
};
}
}
4 changes: 0 additions & 4 deletions web/app/styles/animations.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
}
}

.fade-in-forwards {
animation: fadeIn 450ms forwards;
}

@keyframes fadeOut {
from {
opacity: 1;
Expand Down
15 changes: 2 additions & 13 deletions web/app/types/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,7 @@ import {
import { ProjectStatus } from "./project-status";
import { HermesDocument } from "./document";
import { AlgoliaHit } from "hermes/services/algolia";

export interface JiraIssue {
key: string;
url: string;
priority: string;
priorityImage: string;
status: string;
assignee?: string;
issueType: string;
issueTypeImage: string;
summary: string;
}
import JiraIssueModel from "hermes/models/jira-issue";

export interface JiraPickerResult {
key: string;
Expand Down Expand Up @@ -47,5 +36,5 @@ export interface HermesProjectResources {
export interface HermesProject
extends HermesProjectInfo,
HermesProjectResources {
jiraIssue?: JiraIssue;
jiraIssue?: JiraIssueModel;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { MirageTestContext, setupMirage } from "ember-cli-mirage/test-support";
import { setupRenderingTest } from "ember-qunit";
import { click, fillIn, find, render, waitFor } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import { JiraIssue, JiraPickerResult } from "hermes/types/project";
import { JiraPickerResult } from "hermes/types/project";
import {
TEST_JIRA_WORKSPACE_URL,
TEST_JIRA_ISSUE_SUMMARY,
setWebConfig,
} from "hermes/mirage/utils";
import JiraIssueModel from "hermes/models/jira-issue";

const JIRA_ICON = "[data-test-jira-icon]";
const ADD_JIRA_INPUT = "[data-test-add-jira-input]";
Expand All @@ -33,7 +34,7 @@ const PLUS_ICON = "[data-test-add-jira-button-plus]";

interface Context extends MirageTestContext {
contextIsForm: boolean;
issue: JiraPickerResult | JiraIssue;
issue: JiraPickerResult | JiraIssueModel;
isLoading: boolean;
onIssueSelect: () => void;
onIssueRemove: () => void;
Expand Down
Loading