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

[GH-855] Save user's last used field values #969

Merged
merged 3 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions server/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -1035,8 +1035,8 @@ func executeMe(p *Plugin, c *plugin.Context, header *model.CommandArgs, args ...

resp += connectionBullet(info.User.ConnectedInstances.Get(instanceID), connection, info.User.DefaultInstanceID == instanceID)
resp += fmt.Sprintf(" * %s\n", connection.Settings)
if connection.DefaultProjectKey != "" {
resp += fmt.Sprintf(" * Default project: `%s`\n", connection.DefaultProjectKey)
if connection.SavedFieldValues != nil && connection.SavedFieldValues.ProjectKey != "" {
resp += fmt.Sprintf(" * Default project: `%s`\n", connection.SavedFieldValues.ProjectKey)
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions server/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,11 @@ func (p *Plugin) CreateIssue(in *InCreateIssue) (*jira.Issue, error) {
if err != nil {
return nil, errors.WithMessage(err, "failed to fetch issue details "+created.Key)
}

p.UpdateUserDefaults(in.mattermostUserID, in.InstanceID, project.Key)
p.UpdateUserDefaults(in.mattermostUserID, in.InstanceID, &SavedFieldValues{
ProjectKey: project.Key,
IssueType: issue.Fields.Type.ID,
Components: issue.Fields.Components,
})

// Create a public post for all the channel members
publicReply := &model.Post{
Expand Down Expand Up @@ -459,7 +462,7 @@ func (p *Plugin) GetSearchIssues(instanceID, mattermostUserID types.ID, q, jqlSt
type OutProjectMetadata struct {
Projects []utils.ReactSelectOption `json:"projects"`
IssuesPerProjects map[string][]utils.ReactSelectOption `json:"issues_per_project"`
DefaultProjectKey string `json:"default_project_key,omitempty"`
SavedFieldValues *SavedFieldValues `json:"saved_field_values,omitempty"`
}

func (p *Plugin) httpGetJiraProjectMetadata(w http.ResponseWriter, r *http.Request) (int, error) {
Expand Down Expand Up @@ -526,7 +529,7 @@ func (p *Plugin) httpGetJiraProjectMetadata(w http.ResponseWriter, r *http.Reque
return respondJSON(w, OutProjectMetadata{
Projects: projects,
IssuesPerProjects: issues,
DefaultProjectKey: connection.DefaultProjectKey,
SavedFieldValues: connection.SavedFieldValues,
})
}

Expand Down Expand Up @@ -655,7 +658,7 @@ func (p *Plugin) AttachCommentToIssue(in *InAttachCommentToIssue) (*jira.Comment
rootID = post.RootId
}

p.UpdateUserDefaults(in.mattermostUserID, in.InstanceID, "")
p.UpdateUserDefaults(in.mattermostUserID, in.InstanceID, nil)

msg := fmt.Sprintf("Message attached to [%s](%s/browse/%s)", in.IssueKey, instance.GetJiraBaseURL(), in.IssueKey)

Expand Down
8 changes: 6 additions & 2 deletions server/subscribe.go
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,9 @@ func (p *Plugin) httpChannelCreateSubscription(w http.ResponseWriter, r *http.Re
if subscription.Filters.Projects.Len() == 1 {
projectKey = subscription.Filters.Projects.Elems()[0]
}
p.UpdateUserDefaults(types.ID(mattermostUserID), subscription.InstanceID, projectKey)
p.UpdateUserDefaults(types.ID(mattermostUserID), subscription.InstanceID, &SavedFieldValues{
ProjectKey: projectKey,
})

code, err := respondJSON(w, &subscription)
if err != nil {
Expand Down Expand Up @@ -879,7 +881,9 @@ func (p *Plugin) httpChannelEditSubscription(w http.ResponseWriter, r *http.Requ
if subscription.Filters.Projects.Len() == 1 {
projectKey = subscription.Filters.Projects.Elems()[0]
}
p.UpdateUserDefaults(types.ID(mattermostUserID), subscription.InstanceID, projectKey)
p.UpdateUserDefaults(types.ID(mattermostUserID), subscription.InstanceID, &SavedFieldValues{
ProjectKey: projectKey,
})

code, err := respondJSON(w, &subscription)
if err != nil {
Expand Down
16 changes: 11 additions & 5 deletions server/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ type Connection struct {
Oauth1AccessSecret string `json:",omitempty"`
OAuth2Token *oauth2.Token `json:",omitempty"`
Settings *ConnectionSettings
DefaultProjectKey string `json:"default_project_key,omitempty"`
MattermostUserID types.ID `json:"mattermost_user_id"`
SavedFieldValues *SavedFieldValues `json:"saved_field_values,omitempty"`
Copy link
Contributor

@mickmister mickmister Oct 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are technically losing data here by changing its shape, but the "old" value of DefaultProjectKey is not too important. Just something to think about when changing persistent data structures

MattermostUserID types.ID `json:"mattermost_user_id"`
}

type SavedFieldValues struct {
ProjectKey string `json:"project_key,omitempty"`
IssueType string `json:"issue_type,omitempty"`
Components []*jira.Component `json:"components,omitempty"`
}

func (c *Connection) JiraAccountID() types.ID {
Expand Down Expand Up @@ -153,7 +159,7 @@ func (user *User) AsConfigMap() map[string]interface{} {
}
}

func (p *Plugin) UpdateUserDefaults(mattermostUserID, instanceID types.ID, projectKey string) {
func (p *Plugin) UpdateUserDefaults(mattermostUserID, instanceID types.ID, savedValues *SavedFieldValues) {
user, err := p.userStore.LoadUser(mattermostUserID)
if err != nil {
return
Expand All @@ -174,8 +180,8 @@ func (p *Plugin) UpdateUserDefaults(mattermostUserID, instanceID types.ID, proje
}
}

if projectKey != "" && projectKey != connection.DefaultProjectKey {
connection.DefaultProjectKey = projectKey
if savedValues != nil {
connection.SavedFieldValues = savedValues
err = p.userStore.StoreConnection(instanceID, user.MattermostUserID, connection)
if err != nil {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ describe('components/JiraInstanceAndProjectSelector', () => {
connectedInstances: [{instance_id: 'instance1', type: InstanceType.CLOUD}, {instance_id: 'instance2', type: InstanceType.SERVER}],
defaultUserInstanceID: '',
fetchJiraProjectMetadata: jest.fn().mockResolvedValue({data: {
default_project_key: 'TEST',
saved_field_values: {
project_key: 'TEST',
},
projects: [
{value: 'TEST', label: 'Test Project'},
{value: 'AA', label: 'Apples Arrangement'},
Expand Down Expand Up @@ -120,7 +122,7 @@ describe('components/JiraInstanceAndProjectSelector', () => {
expect(props.onInstanceChange).not.toBeCalled();
});

test('should use default project key after fetch', async () => {
test('should use default field values after fetch', async () => {
const props = {
...baseProps,
defaultUserInstanceID: 'instance2',
Expand All @@ -133,7 +135,9 @@ describe('components/JiraInstanceAndProjectSelector', () => {
expect(wrapper.state().fetchingProjectMetadata).toBe(true);

await props.fetchJiraProjectMetadata('');
expect(props.onProjectChange).toBeCalledWith('TEST');
expect(props.onProjectChange).toBeCalledWith({
project_key: 'TEST',
});
});

test('should pass error on failed fetch', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import React from 'react';

import {Theme} from 'mattermost-redux/types/preferences';

import {Instance, ProjectMetadata, ReactSelectOption, APIResponse, GetConnectedResponse} from 'types/model';
import {Instance, ProjectMetadata, ReactSelectOption, APIResponse, GetConnectedResponse, SavedFieldValues} from 'types/model';
import ReactSelectSetting from 'components/react_select_setting';
import {getProjectValues} from 'utils/jira_issue_metadata';

export type Props = {
selectedInstanceID: string | null;
selectedProjectID: string | null;
onInstanceChange: (instanceID: string) => void;
onProjectChange: (projectID: string) => void;
onProjectChange: (fieldValues: SavedFieldValues) => void;
onError: (err: string) => void;

theme: Theme;
Expand Down Expand Up @@ -95,8 +95,8 @@ export default class JiraInstanceAndProjectSelector extends React.PureComponent<
fetchingProjectMetadata: false,
});

if (projectMetadata.default_project_key && !this.props.selectedProjectID) {
this.props.onProjectChange(projectMetadata.default_project_key);
if (projectMetadata.saved_field_values && projectMetadata.saved_field_values.project_key && !this.props.selectedProjectID) {
this.props.onProjectChange(projectMetadata.saved_field_values);
}
}

Expand All @@ -112,7 +112,9 @@ export default class JiraInstanceAndProjectSelector extends React.PureComponent<
}

handleProjectChange = (_: string, projectID: string) => {
this.props.onProjectChange(projectID);
this.props.onProjectChange({
project_key: projectID,
});
}

render() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Post} from 'mattermost-redux/types/posts';
import {Team} from 'mattermost-redux/types/teams';
import {Theme} from 'mattermost-redux/types/preferences';

import {APIResponse, AttachCommentRequest} from 'types/model';
import {APIResponse, AttachCommentRequest, SavedFieldValues} from 'types/model';

import {getModalStyles} from 'utils/styles';

Expand Down Expand Up @@ -94,7 +94,7 @@ export default class AttachCommentToIssueForm extends PureComponent<Props, State
selectedProjectID={''}
hideProjectSelector={true}
onInstanceChange={(instanceID: string) => this.setState({instanceID})}
onProjectChange={(projectKey: string) => {}}
onProjectChange={(savedValues: SavedFieldValues) => {}}
theme={this.props.theme}
addValidate={this.validator.addComponent}
removeValidate={this.validator.removeComponent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ describe('components/EditChannelSubscription', () => {
<EditChannelSubscription {...props}/>
);
wrapper.setState(baseState);
wrapper.instance().handleProjectChange('TES');
wrapper.instance().handleProjectChange({
project_key: 'TES',
});
expect(wrapper.state().filters.projects).toEqual(['TES']);
expect(wrapper.state().fetchingIssueMetadata).toBe(true);
expect(fetchJiraIssueMetadataForProjects).toHaveBeenCalled();
Expand All @@ -150,7 +152,9 @@ describe('components/EditChannelSubscription', () => {
fetchJiraIssueMetadataForProjects = jest.fn().mockResolvedValue({error: {message: 'Failure'}});
wrapper.setProps({fetchJiraIssueMetadataForProjects});

wrapper.instance().handleProjectChange('KT');
wrapper.instance().handleProjectChange({
project_key: 'KT',
});
expect(wrapper.state().filters.projects).toEqual(['KT']);
expect(fetchJiraIssueMetadataForProjects).toHaveBeenCalled();
expect(wrapper.state().fetchingIssueMetadata).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
filterValueIsSecurityField,
} from 'utils/jira_issue_metadata';

import {ChannelSubscription, ChannelSubscriptionFilters as ChannelSubscriptionFiltersModel, ReactSelectOption, FilterValue, IssueMetadata} from 'types/model';
import {ChannelSubscription, ChannelSubscriptionFilters as ChannelSubscriptionFiltersModel, ReactSelectOption, FilterValue, IssueMetadata, SavedFieldValues} from 'types/model';

import ChannelSubscriptionFilters from './channel_subscription_filters';
import {SharedProps} from './shared_props';
Expand Down Expand Up @@ -244,10 +244,11 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
}

this.setState({instanceID, error: null});
this.handleProjectChange('');
this.handleProjectChange({});
}

handleProjectChange = (projectID: string) => {
handleProjectChange = (fieldValues: SavedFieldValues) => {
const projectID = fieldValues.project_key ? fieldValues.project_key : '';
this.clearConflictingErrorMessage();

let projects: string[];
Expand Down
30 changes: 21 additions & 9 deletions webapp/src/components/modals/create_issue/create_issue_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Theme} from 'mattermost-redux/types/preferences';
import {Post} from 'mattermost-redux/types/posts';
import {Team} from 'mattermost-redux/types/teams';

import {APIResponse, IssueMetadata, CreateIssueRequest, JiraFieldTypeEnums, JiraFieldCustomTypeEnums, CreateIssueFields, JiraField} from 'types/model';
import {APIResponse, IssueMetadata, CreateIssueRequest, JiraFieldTypeEnums, JiraFieldCustomTypeEnums, CreateIssueFields, JiraField, SavedFieldValues} from 'types/model';

import {getFields, getIssueTypes} from 'utils/jira_issue_metadata';
import {getModalStyles} from 'utils/styles';
Expand Down Expand Up @@ -115,7 +115,8 @@ export default class CreateIssueForm extends React.PureComponent<Props, State> {
this.setState({instanceID, projectKey: '', error: null});
}

handleProjectChange = (projectKey: string) => {
handleProjectChange = (fieldValues: SavedFieldValues) => {
const projectKey = fieldValues.project_key ? fieldValues.project_key : '';
this.setState({projectKey, fetchingIssueMetadata: true, error: null});

this.props.fetchJiraIssueMetadataForProjects([projectKey], this.state.instanceID as string).then(({data, error}) => {
Expand All @@ -132,21 +133,32 @@ export default class CreateIssueForm extends React.PureComponent<Props, State> {
this.setState(state);
});

const fields = {
let fields = {
summary: this.state.fields.summary,
description: this.state.fields.description,
project: {key: projectKey},
} as CreateIssueFields;

const issueTypes = getIssueTypes(this.state.jiraIssueMetadata, projectKey);
const issueType = issueTypes.length ? issueTypes[0].id : '';
fields.issuetype = {
id: issueType,
};
if (fieldValues.issue_type) {
fields.issuetype = {
id: fieldValues.issue_type,
};
} else {
const issueTypes = getIssueTypes(this.state.jiraIssueMetadata, projectKey);
const issueType = issueTypes.length ? issueTypes[0].id : '';
fields.issuetype = {
id: issueType,
};
}

fields = {...fields};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mickmister Fixed

if (fieldValues) {
fields.components = fieldValues.components as JiraField;
}

this.setState({
projectKey,
issueType,
issueType: fieldValues.issue_type ? fieldValues.issue_type : '',
fields,
});
}
Expand Down
8 changes: 7 additions & 1 deletion webapp/src/types/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ export type IssueMetadata = {
export type ProjectMetadata = {
projects: ReactSelectOption[];
issues_per_project: {[key: string]: ReactSelectOption[]};
default_project_key?: string;
saved_field_values?: SavedFieldValues;
}

export type SavedFieldValues = {
project_key?: string;
issue_type?: string;
components?: JiraField;
}

export enum JiraFieldTypeEnums {
Expand Down
Loading