settings.tsx 5.6 KB

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