settings.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import isEqual from 'lodash/isEqual';
  2. import {Form, FormState} from 'sentry/components/deprecatedforms';
  3. import LoadingIndicator from 'sentry/components/loadingIndicator';
  4. import DefaultSettings from 'sentry/plugins/components/settings';
  5. type Field = Parameters<typeof DefaultSettings.prototype.renderField>[0]['config'];
  6. type FieldWithValues = Field & {defaultValue?: any; value?: any};
  7. type ApiData = {config: FieldWithValues[]; default_project?: string};
  8. type Props = DefaultSettings['props'];
  9. type State = DefaultSettings['state'] & {
  10. page: number;
  11. editing?: boolean;
  12. };
  13. const PAGE_FIELD_LIST = {
  14. 0: ['instance_url', 'username', 'password'],
  15. 1: ['default_project'],
  16. 2: ['ignored_fields', 'default_priority', 'default_issue_type', 'auto_create'],
  17. };
  18. class Settings extends DefaultSettings<Props, State> {
  19. constructor(props: Props, context: any) {
  20. super(props, context);
  21. Object.assign(this.state, {
  22. page: 0,
  23. });
  24. }
  25. isConfigured() {
  26. return !!(this.state.formData && this.state.formData.default_project);
  27. }
  28. isLastPage = () => {
  29. return this.state.page === 2;
  30. };
  31. fetchData() {
  32. // This is mostly copy paste of parent class
  33. // except for setting edit state
  34. this.api.request(this.getPluginEndpoint(), {
  35. success: (data: ApiData) => {
  36. const formData: Record<string, any> = {};
  37. const initialData = {};
  38. data.config.forEach(field => {
  39. formData[field.name] = field.value || field.defaultValue;
  40. initialData[field.name] = field.value;
  41. });
  42. this.setState(
  43. {
  44. fieldList: data.config,
  45. formData,
  46. initialData,
  47. // start off in edit mode if there isn't a project set
  48. editing: !(formData && formData.default_project),
  49. // call this here to prevent FormState.READY from being
  50. // set before fieldList is
  51. },
  52. this.onLoadSuccess
  53. );
  54. },
  55. error: this.onLoadError,
  56. });
  57. }
  58. startEditing = () => {
  59. this.setState({editing: true});
  60. };
  61. onSubmit() {
  62. if (isEqual(this.state.initialData, this.state.formData)) {
  63. if (this.isLastPage()) {
  64. this.setState({editing: false, page: 0});
  65. } else {
  66. this.setState({page: this.state.page + 1});
  67. }
  68. this.onSaveSuccess(this.onSaveComplete);
  69. return;
  70. }
  71. const body = Object.assign({}, this.state.formData);
  72. // if the project has changed, it's likely these values aren't valid anymore
  73. if (body.default_project !== this.state.initialData?.default_project) {
  74. body.default_issue_type = null;
  75. body.default_priority = null;
  76. }
  77. this.api.request(this.getPluginEndpoint(), {
  78. data: body,
  79. method: 'PUT',
  80. success: this.onSaveSuccess.bind(this, (data: ApiData) => {
  81. const formData = {};
  82. const initialData = {};
  83. data.config.forEach(field => {
  84. formData[field.name] = field.value || field.defaultValue;
  85. initialData[field.name] = field.value;
  86. });
  87. const state = {
  88. formData,
  89. initialData,
  90. errors: {},
  91. fieldList: data.config,
  92. page: this.state.page,
  93. editing: this.state.editing,
  94. };
  95. if (this.isLastPage()) {
  96. state.editing = false;
  97. state.page = 0;
  98. } else {
  99. state.page = this.state.page + 1;
  100. }
  101. this.setState(state);
  102. }),
  103. error: this.onSaveError.bind(this, error => {
  104. this.setState({
  105. errors: (error.responseJSON || {}).errors || {},
  106. });
  107. }),
  108. complete: this.onSaveComplete,
  109. });
  110. }
  111. back = (ev: React.MouseEvent) => {
  112. ev.preventDefault();
  113. if (this.state.state === FormState.SAVING) {
  114. return;
  115. }
  116. this.setState({
  117. page: this.state.page - 1,
  118. });
  119. };
  120. render() {
  121. if (this.state.state === FormState.LOADING) {
  122. return <LoadingIndicator />;
  123. }
  124. if (this.state.state === FormState.ERROR && !this.state.fieldList) {
  125. return (
  126. <div className="alert alert-error m-b-1">
  127. An unknown error occurred. Need help with this?{' '}
  128. <a href="https://sentry.io/support/">Contact support</a>
  129. </div>
  130. );
  131. }
  132. const isSaving = this.state.state === FormState.SAVING;
  133. let fields: Field[] | undefined;
  134. let onSubmit: () => void;
  135. let submitLabel: string;
  136. if (this.state.editing) {
  137. fields = this.state.fieldList?.filter(f =>
  138. PAGE_FIELD_LIST[this.state.page].includes(f.name)
  139. );
  140. onSubmit = this.onSubmit;
  141. submitLabel = this.isLastPage() ? 'Finish' : 'Save and Continue';
  142. } else {
  143. fields = this.state.fieldList?.map(f => ({...f, readonly: true}));
  144. onSubmit = this.startEditing;
  145. submitLabel = 'Edit';
  146. }
  147. return (
  148. <Form
  149. onSubmit={onSubmit}
  150. submitDisabled={isSaving}
  151. submitLabel={submitLabel}
  152. extraButton={
  153. this.state.page === 0 ? null : (
  154. <a
  155. href="#"
  156. className={'btn btn-default pull-left' + (isSaving ? ' disabled' : '')}
  157. onClick={this.back}
  158. >
  159. Back
  160. </a>
  161. )
  162. }
  163. >
  164. {this.state.errors.__all__ && (
  165. <div className="alert alert-block alert-error">
  166. <ul>
  167. <li>{this.state.errors.__all__}</li>
  168. </ul>
  169. </div>
  170. )}
  171. {fields?.map(f =>
  172. this.renderField({
  173. config: f,
  174. formData: this.state.formData,
  175. formErrors: this.state.errors,
  176. onChange: this.changeField.bind(this, f.name),
  177. })
  178. )}
  179. </Form>
  180. );
  181. }
  182. }
  183. export default Settings;