atom / github
Showing 22 of 45 files from the diff.

@@ -42,7 +42,7 @@
Loading
42 42
    tooltips: PropTypes.object.isRequired,
43 43
44 44
    // Action methods
45 -
    reportMutationErrors: PropTypes.func.isRequired,
45 +
    reportRelayError: PropTypes.func.isRequired,
46 46
  }
47 47
48 48
  render() {
@@ -58,6 +58,17 @@
Loading
58 58
      return <LoadingView />;
59 59
    }
60 60
61 +
    if (token instanceof Error) {
62 +
      return (
63 +
        <QueryErrorView
64 +
          error={token}
65 +
          retry={this.handleTokenRetry}
66 +
          login={this.handleLogin}
67 +
          logout={this.handleLogout}
68 +
        />
69 +
      );
70 +
    }
71 +
61 72
    if (token === UNAUTHENTICATED) {
62 73
      return <GithubLoginView onLogin={this.handleLogin} />;
63 74
    }
@@ -173,14 +184,16 @@
Loading
173 184
    }
174 185
175 186
    return (
176 -
      <AggregatedReviewsContainer pullRequest={props.repository.pullRequest}>
187 +
      <AggregatedReviewsContainer
188 +
        pullRequest={props.repository.pullRequest}
189 +
        reportRelayError={this.props.reportRelayError}>
177 190
        {({errors, summaries, commentThreads, refetch}) => {
178 191
          if (errors && errors.length > 0) {
179 192
            return errors.map((err, i) => (
180 193
              <ErrorView
181 194
                key={`error-${i}`}
182 195
                title="Pagination error"
183 -
                descriptions={[err.stack || err.toString()]}
196 +
                descriptions={[err.stack]}
184 197
              />
185 198
            ));
186 199
          }
@@ -241,4 +254,6 @@
Loading
241 254
  handleLogin = token => this.props.loginModel.setToken(this.props.endpoint.getLoginAccount(), token);
242 255
243 256
  handleLogout = () => this.props.loginModel.removeToken(this.props.endpoint.getLoginAccount());
257 +
258 +
  handleTokenRetry = () => this.props.loginModel.didUpdate();
244 259
}

@@ -95,7 +95,7 @@
Loading
95 95
    resolveThread: PropTypes.func.isRequired,
96 96
    unresolveThread: PropTypes.func.isRequired,
97 97
    addSingleComment: PropTypes.func.isRequired,
98 -
    reportMutationErrors: PropTypes.func.isRequired,
98 +
    reportRelayError: PropTypes.func.isRequired,
99 99
  }
100 100
101 101
  constructor(props) {
@@ -287,7 +287,7 @@
Loading
287 287
          <EmojiReactionsController
288 288
            reactable={review}
289 289
            tooltips={this.props.tooltips}
290 -
            reportMutationErrors={this.props.reportMutationErrors}
290 +
            reportRelayError={this.props.reportRelayError}
291 291
          />
292 292
        </main>
293 293
      </div>
@@ -581,7 +581,7 @@
Loading
581 581
          <EmojiReactionsController
582 582
            reactable={comment}
583 583
            tooltips={this.props.tooltips}
584 -
            reportMutationErrors={this.props.reportMutationErrors}
584 +
            reportRelayError={this.props.reportRelayError}
585 585
          />
586 586
        </div>
587 587
      </div>

@@ -19,7 +19,7 @@
Loading
19 19
    tooltips: PropTypes.object.isRequired,
20 20
21 21
    // Action methods
22 -
    reportMutationErrors: PropTypes.func.isRequired,
22 +
    reportRelayError: PropTypes.func.isRequired,
23 23
  }
24 24
25 25
  render() {
@@ -36,7 +36,7 @@
Loading
36 36
    try {
37 37
      await addReactionMutation(this.props.relay.environment, this.props.reactable.id, content);
38 38
    } catch (err) {
39 -
      this.props.reportMutationErrors('Unable to add reaction emoji', err);
39 +
      this.props.reportRelayError('Unable to add reaction emoji', err);
40 40
    }
41 41
  };
42 42
@@ -44,7 +44,7 @@
Loading
44 44
    try {
45 45
      await removeReactionMutation(this.props.relay.environment, this.props.reactable.id, content);
46 46
    } catch (err) {
47 -
      this.props.reportMutationErrors('Unable to remove reaction emoji', err);
47 +
      this.props.reportRelayError('Unable to remove reaction emoji', err);
48 48
    }
49 49
  };
50 50
}

@@ -27,7 +27,7 @@
Loading
27 27
    tooltips: PropTypes.object.isRequired,
28 28
29 29
    // Action methods
30 -
    reportMutationErrors: PropTypes.func.isRequired,
30 +
    reportRelayError: PropTypes.func.isRequired,
31 31
  }
32 32
33 33
  static uriPattern = 'atom-github://reviews/{host}/{owner}/{repo}/{number}?workdir={workdir}'

@@ -61,7 +61,7 @@
Loading
61 61
    onTitleChange: PropTypes.func.isRequired,
62 62
    switchToIssueish: PropTypes.func.isRequired,
63 63
    destroy: PropTypes.func.isRequired,
64 -
    reportMutationErrors: PropTypes.func.isRequired,
64 +
    reportRelayError: PropTypes.func.isRequired,
65 65
66 66
    // Item context
67 67
    itemType: ItemTypePropType.isRequired,
@@ -147,7 +147,7 @@
Loading
147 147
              openReviews={this.openReviews}
148 148
              switchToIssueish={this.props.switchToIssueish}
149 149
              destroy={this.props.destroy}
150 -
              reportMutationErrors={this.props.reportMutationErrors}
150 +
              reportRelayError={this.props.reportRelayError}
151 151
152 152
              itemType={this.props.itemType}
153 153
              refEditor={this.props.refEditor}
@@ -170,7 +170,7 @@
Loading
170 170
          issue={repository.issue}
171 171
          switchToIssueish={this.props.switchToIssueish}
172 172
          tooltips={this.props.tooltips}
173 -
          reportMutationErrors={this.props.reportMutationErrors}
173 +
          reportRelayError={this.props.reportRelayError}
174 174
        />
175 175
      );
176 176
    }

@@ -80,9 +80,7 @@
Loading
80 80
          this.checked.set(fingerprint, true);
81 81
        } catch (e) {
82 82
          // Most likely a network error. Do not cache the failure.
83 -
          // eslint-disable-next-line no-console
84 -
          console.error(`Unable to validate token scopes against ${account}`, e);
85 -
          return UNAUTHENTICATED;
83 +
          return e;
86 84
        }
87 85
      }
88 86
    }
@@ -112,17 +110,26 @@
Loading
112 110
      throw new Error('Attempt to check token scopes in specs');
113 111
    }
114 112
115 -
    const response = await fetch(host, {
116 -
      method: 'HEAD',
117 -
      headers: {Authorization: `bearer ${token}`},
118 -
    });
113 +
    let response;
114 +
    try {
115 +
      response = await fetch(host, {
116 +
        method: 'HEAD',
117 +
        headers: {Authorization: `bearer ${token}`},
118 +
      });
119 +
    } catch (e) {
120 +
      e.network = true;
121 +
      throw e;
122 +
    }
119 123
120 124
    if (response.status === 401) {
121 125
      return UNAUTHORIZED;
122 126
    }
123 127
124 128
    if (response.status !== 200) {
125 -
      throw new Error(`Unable to check token for OAuth scopes against ${host}: ${await response.text()}`);
129 +
      const e = new Error(`Unable to check token for OAuth scopes against ${host}`);
130 +
      e.response = response;
131 +
      e.responseText = await response.text();
132 +
      throw e;
126 133
    }
127 134
128 135
    return response.headers.get('X-OAuth-Scopes').split(/\s*,\s*/);

@@ -3,7 +3,6 @@
Loading
3 3
import {QueryRenderer, graphql} from 'react-relay';
4 4
5 5
import {incrementCounter} from '../reporter-proxy';
6 -
import {autobind} from '../helpers';
7 6
import {
8 7
  RemotePropType, RemoteSetPropType, BranchSetPropType, OperationStateObserverPropType, EndpointPropType,
9 8
} from '../prop-types';
@@ -35,13 +34,7 @@
Loading
35 34
    onPushBranch: PropTypes.func.isRequired,
36 35
  }
37 36
38 -
  constructor(props) {
39 -
    super(props);
40 -
41 -
    autobind(this, 'fetchToken', 'renderWithToken', 'renderWithResult', 'handleLogin', 'handleLogout');
42 -
  }
43 -
44 -
  fetchToken(loginModel) {
37 +
  fetchToken = loginModel => {
45 38
    return loginModel.getToken(this.props.endpoint.getLoginAccount());
46 39
  }
47 40
@@ -53,11 +46,22 @@
Loading
53 46
    );
54 47
  }
55 48
56 -
  renderWithToken(token) {
49 +
  renderWithToken = token => {
57 50
    if (token === null) {
58 51
      return <LoadingView />;
59 52
    }
60 53
54 +
    if (token instanceof Error) {
55 +
      return (
56 +
        <QueryErrorView
57 +
          error={token}
58 +
          retry={this.handleTokenRetry}
59 +
          login={this.handleLogin}
60 +
          logout={this.handleLogout}
61 +
        />
62 +
      );
63 +
    }
64 +
61 65
    if (token === UNAUTHENTICATED) {
62 66
      return <GithubLoginView onLogin={this.handleLogin} />;
63 67
    }
@@ -137,13 +141,15 @@
Loading
137 141
    );
138 142
  }
139 143
140 -
  handleLogin(token) {
144 +
  handleLogin = token => {
141 145
    incrementCounter('github-login');
142 146
    this.props.loginModel.setToken(this.props.endpoint.getLoginAccount(), token);
143 147
  }
144 148
145 -
  handleLogout() {
149 +
  handleLogout = () => {
146 150
    incrementCounter('github-logout');
147 151
    this.props.loginModel.removeToken(this.props.endpoint.getLoginAccount());
148 152
  }
153 +
154 +
  handleTokenRetry = () => this.props.loginModel.didUpdate();
149 155
}

@@ -1,8 +1,6 @@
Loading
1 1
import React from 'react';
2 2
import PropTypes from 'prop-types';
3 3
4 -
import {autobind} from '../helpers';
5 -
6 4
export default class ErrorView extends React.Component {
7 5
  static propTypes = {
8 6
    title: PropTypes.string,
@@ -19,12 +17,6 @@
Loading
19 17
    preformatted: false,
20 18
  }
21 19
22 -
  constructor(props) {
23 -
    super(props);
24 -
25 -
    autobind(this, 'renderDescription');
26 -
  }
27 -
28 20
  render() {
29 21
    return (
30 22
      <div className="github-Message">
@@ -44,7 +36,7 @@
Loading
44 36
    );
45 37
  }
46 38
47 -
  renderDescription(description, key) {
39 +
  renderDescription = (description, key) => {
48 40
    if (this.props.preformatted) {
49 41
      return (
50 42
        <pre key={key} className="github-Message-description">

@@ -66,7 +66,7 @@
Loading
66 66
    tooltips: PropTypes.object.isRequired,
67 67
68 68
    // Action methods
69 -
    reportMutationErrors: PropTypes.func.isRequired,
69 +
    reportRelayError: PropTypes.func.isRequired,
70 70
  }
71 71
72 72
  constructor(props) {
@@ -262,7 +262,7 @@
Loading
262 262
        addEvent('resolve-comment-thread', {package: 'github'});
263 263
      } catch (err) {
264 264
        this.showThreadID(thread.id);
265 -
        this.props.reportMutationErrors('Unable to resolve the comment thread', err);
265 +
        this.props.reportRelayError('Unable to resolve the comment thread', err);
266 266
      }
267 267
    }
268 268
  }
@@ -278,7 +278,7 @@
Loading
278 278
        this.highlightThread(thread.id);
279 279
        addEvent('unresolve-comment-thread', {package: 'github'});
280 280
      } catch (err) {
281 -
        this.props.reportMutationErrors('Unable to unresolve the comment thread', err);
281 +
        this.props.reportRelayError('Unable to unresolve the comment thread', err);
282 282
      }
283 283
    }
284 284
  }
@@ -337,7 +337,7 @@
Loading
337 337
        }
338 338
      }
339 339
340 -
      this.props.reportMutationErrors('Unable to submit your comment', error);
340 +
      this.props.reportRelayError('Unable to submit your comment', error);
341 341
    } finally {
342 342
      this.setState({postingToThreadID: null});
343 343
    }

@@ -3,7 +3,6 @@
Loading
3 3
import yubikiri from 'yubikiri';
4 4
5 5
import {GithubLoginModelPropType, RefHolderPropType} from '../prop-types';
6 -
import {autobind} from '../helpers';
7 6
import OperationStateObserver, {PUSH, PULL, FETCH} from '../models/operation-state-observer';
8 7
import GitHubTabController from '../controllers/github-tab-controller';
9 8
import ObserveModel from '../views/observe-model';
@@ -18,12 +17,7 @@
Loading
18 17
    rootHolder: RefHolderPropType.isRequired,
19 18
  }
20 19
21 -
  constructor(props) {
22 -
    super(props);
23 -
    autobind(this, 'fetchRepositoryData', 'renderRepositoryData');
24 -
25 -
    this.state = {};
26 -
  }
20 +
  state = {};
27 21
28 22
  static getDerivedStateFromProps(props, state) {
29 23
    if (props.repository !== state.lastRepository) {
@@ -36,7 +30,7 @@
Loading
36 30
    return null;
37 31
  }
38 32
39 -
  fetchRepositoryData(repository) {
33 +
  fetchRepositoryData = repository => {
40 34
    return yubikiri({
41 35
      workingDirectory: repository.getWorkingDirectoryPath(),
42 36
      allRemotes: repository.getRemotes(),
@@ -59,7 +53,7 @@
Loading
59 53
    );
60 54
  }
61 55
62 -
  renderRepositoryData(data) {
56 +
  renderRepositoryData = data => {
63 57
    if (!data || this.props.repository.isLoading()) {
64 58
      return (
65 59
        <GitHubTabController

@@ -20,6 +20,8 @@
Loading
20 20
    commands: PropTypes.object.isRequired,
21 21
    localRepository: PropTypes.object.isRequired,
22 22
    loginModel: GithubLoginModelPropType.isRequired,
23 +
24 +
    reportRelayError: PropTypes.func.isRequired,
23 25
  };
24 26
25 27
  render() {
@@ -46,7 +48,7 @@
Loading
46 48
  }
47 49
48 50
  renderWithToken(token, {repoData}) {
49 -
    if (!token || token === UNAUTHENTICATED || token === INSUFFICIENT) {
51 +
    if (!token || token === UNAUTHENTICATED || token === INSUFFICIENT || token instanceof Error) {
50 52
      // we're not going to prompt users to log in to render decorations for comments
51 53
      // just let it go and move on with our lives.
52 54
      return null;
@@ -177,7 +179,9 @@
Loading
177 179
    }
178 180
179 181
    return (
180 -
      <AggregatedReviewsContainer pullRequest={queryProps.currentPullRequest}>
182 +
      <AggregatedReviewsContainer
183 +
        pullRequest={queryProps.currentPullRequest}
184 +
        reportRelayError={this.props.reportRelayError}>
181 185
        {({errors, summaries, commentThreads}) => {
182 186
          if (errors && errors.length > 0) {
183 187
            // eslint-disable-next-line no-console

@@ -55,7 +55,7 @@
Loading
55 55
    tooltips: PropTypes.object.isRequired,
56 56
57 57
    // Action methods
58 -
    reportMutationErrors: PropTypes.func.isRequired,
58 +
    reportRelayError: PropTypes.func.isRequired,
59 59
  }
60 60
61 61
  state = {
@@ -87,7 +87,7 @@
Loading
87 87
        <EmojiReactionsController
88 88
          reactable={issue}
89 89
          tooltips={this.props.tooltips}
90 -
          reportMutationErrors={this.props.reportMutationErrors}
90 +
          reportRelayError={this.props.reportRelayError}
91 91
        />
92 92
        <IssueTimelineController
93 93
          issue={issue}
@@ -173,7 +173,10 @@
Loading
173 173
      issueishId: this.props.issue.id,
174 174
      timelineCount: 100,
175 175
      timelineCursor: null,
176 -
    }, null, () => {
176 +
    }, null, err => {
177 +
      if (err) {
178 +
        this.props.reportRelayError('Unable to refresh issue details', err);
179 +
      }
177 180
      this.setState({refreshing: false});
178 181
    }, {force: true});
179 182
  }

@@ -42,7 +42,7 @@
Loading
42 42
    config: PropTypes.object.isRequired,
43 43
44 44
    // Action methods
45 -
    reportMutationErrors: PropTypes.func.isRequired,
45 +
    reportRelayError: PropTypes.func.isRequired,
46 46
  }
47 47
48 48
  static defaultProps = {
@@ -124,7 +124,7 @@
Loading
124 124
        destroy={this.destroy}
125 125
        itemType={this.constructor}
126 126
        refEditor={this.refEditor}
127 -
        reportMutationErrors={this.props.reportMutationErrors}
127 +
        reportRelayError={this.props.reportRelayError}
128 128
      />
129 129
    );
130 130
  }

@@ -48,7 +48,7 @@
Loading
48 48
    switchToIssueish: PropTypes.func.isRequired,
49 49
    onTitleChange: PropTypes.func.isRequired,
50 50
    destroy: PropTypes.func.isRequired,
51 -
    reportMutationErrors: PropTypes.func.isRequired,
51 +
    reportRelayError: PropTypes.func.isRequired,
52 52
53 53
    // Item context
54 54
    itemType: ItemTypePropType.isRequired,
@@ -66,6 +66,17 @@
Loading
66 66
  renderWithToken = tokenData => {
67 67
    const token = tokenData && tokenData.token;
68 68
69 +
    if (token instanceof Error) {
70 +
      return (
71 +
        <QueryErrorView
72 +
          error={token}
73 +
          login={this.handleLogin}
74 +
          retry={this.handleTokenRetry}
75 +
          logout={this.handleLogout}
76 +
        />
77 +
      );
78 +
    }
79 +
69 80
    if (token === UNAUTHENTICATED) {
70 81
      return <GithubLoginView onLogin={this.handleLogin} />;
71 82
    }
@@ -193,7 +204,9 @@
Loading
193 204
194 205
    if (props.repository.issueish.__typename === 'PullRequest') {
195 206
      return (
196 -
        <AggregatedReviewsContainer pullRequest={props.repository.issueish}>
207 +
        <AggregatedReviewsContainer
208 +
          pullRequest={props.repository.issueish}
209 +
          reportRelayError={this.props.reportRelayError}>
197 210
          {aggregatedReviews => this.renderWithCommentResult(token, repoData, {props, retry}, aggregatedReviews)}
198 211
        </AggregatedReviewsContainer>
199 212
      );
@@ -247,7 +260,7 @@
Loading
247 260
        onTabSelected={this.props.onTabSelected}
248 261
        onOpenFilesTab={this.props.onOpenFilesTab}
249 262
        endpoint={this.props.endpoint}
250 -
        reportMutationErrors={this.props.reportMutationErrors}
263 +
        reportRelayError={this.props.reportRelayError}
251 264
252 265
        workspace={this.props.workspace}
253 266
        commands={this.props.commands}
@@ -283,4 +296,6 @@
Loading
283 296
  handleLogin = token => this.props.loginModel.setToken(this.props.endpoint.getLoginAccount(), token);
284 297
285 298
  handleLogout = () => this.props.loginModel.removeToken(this.props.endpoint.getLoginAccount());
299 +
300 +
  handleTokenRetry = () => this.props.loginModel.didUpdate();
286 301
}

@@ -4,7 +4,6 @@
Loading
4 4
import {
5 5
  GithubLoginModelPropType, RefHolderPropType, RemoteSetPropType, BranchSetPropType, OperationStateObserverPropType,
6 6
} from '../prop-types';
7 -
import {autobind} from '../helpers';
8 7
import GitHubTabView from '../views/github-tab-view';
9 8
10 9
export default class GitHubTabController extends React.Component {
@@ -24,11 +23,6 @@
Loading
24 23
    isLoading: PropTypes.bool.isRequired,
25 24
  }
26 25
27 -
  constructor(props) {
28 -
    super(props);
29 -
    autobind(this, 'handlePushBranch', 'handleRemoteSelect');
30 -
  }
31 -
32 26
  render() {
33 27
    const gitHubRemotes = this.props.allRemotes.filter(remote => remote.isGithubRepo());
34 28
    const currentBranch = this.props.branches.getHeadBranch();
@@ -64,14 +58,14 @@
Loading
64 58
    );
65 59
  }
66 60
67 -
  handlePushBranch(currentBranch, targetRemote) {
61 +
  handlePushBranch = (currentBranch, targetRemote) => {
68 62
    return this.props.repository.push(currentBranch.getName(), {
69 63
      remote: targetRemote,
70 64
      setUpstream: true,
71 65
    });
72 66
  }
73 67
74 -
  handleRemoteSelect(e, remote) {
68 +
  handleRemoteSelect = (e, remote) => {
75 69
    e.preventDefault();
76 70
    return this.props.repository.setConfig('atomGithub.currentRemote', remote.getName());
77 71
  }

@@ -10,6 +10,7 @@
Loading
10 10
        status: PropTypes.number.isRequired,
11 11
      }),
12 12
      responseText: PropTypes.string,
13 +
      network: PropTypes.bool,
13 14
      errors: PropTypes.arrayOf(PropTypes.shape({
14 15
        message: PropTypes.string.isRequired,
15 16
      })),
@@ -34,21 +35,25 @@
Loading
34 35
  renderMessages() {
35 36
    if (this.props.error.errors) {
36 37
      return this.props.error.errors.map((error, index) => {
37 -
        return this.renderMessage(error.message, index);
38 +
        return this.renderMessage(error.message, index, 'alert');
38 39
      });
39 40
    }
40 41
41 42
    if (this.props.error.response) {
42 -
      return this.renderMessage(this.props.error.responseText, '0');
43 +
      return this.renderMessage(this.props.error.responseText, '0', 'alert');
43 44
    }
44 45
45 -
    return this.renderMessage(this.props.error.toString(), '0');
46 +
    if (this.props.error.network) {
47 +
      return this.renderMessage('Offline', '0', 'alignment-unalign');
48 +
    }
49 +
50 +
    return this.renderMessage(this.props.error.toString(), '0', 'alert');
46 51
  }
47 52
48 -
  renderMessage(body, key) {
53 +
  renderMessage(body, key, icon) {
49 54
    return (
50 55
      <p key={key} className="github-QueryErrorTile-message">
51 -
        <Octicon icon="alert" />
56 +
        <Octicon icon={icon} />
52 57
        {body}
53 58
      </p>
54 59
    );

@@ -0,0 +1,35 @@
Loading
1 +
import React from 'react';
2 +
import PropTypes from 'prop-types';
3 +
4 +
import Octicon from '../atom/octicon';
5 +
6 +
export default class OfflineView extends React.Component {
7 +
  static propTypes = {
8 +
    retry: PropTypes.func.isRequired,
9 +
  }
10 +
11 +
  componentDidMount() {
12 +
    window.addEventListener('online', this.props.retry);
13 +
  }
14 +
15 +
  componentWillUnmount() {
16 +
    window.removeEventListener('online', this.props.retry);
17 +
  }
18 +
19 +
  render() {
20 +
    return (
21 +
      <div className="github-Offline github-Message">
22 +
        <div className="github-Message-wrapper">
23 +
          <Octicon className="github-Offline-logo" icon="alignment-unalign" />
24 +
          <h1 className="github-Message-title">Offline</h1>
25 +
          <p className="github-Message-description">
26 +
            You don't seem to be connected to the Internet. When you're back online, we'll try again.
27 +
          </p>
28 +
          <p className="github-Message-action">
29 +
            <button className="github-Message-button btn" onClick={this.props.retry}>Retry</button>
30 +
          </p>
31 +
        </div>
32 +
      </div>
33 +
    );
34 +
  }
35 +
}

@@ -3,6 +3,7 @@
Loading
3 3
4 4
import GithubLoginView from './github-login-view';
5 5
import ErrorView from './error-view';
6 +
import OfflineView from './offline-view';
6 7
7 8
export default class QueryErrorView extends React.Component {
8 9
  static propTypes = {
@@ -40,6 +41,10 @@
Loading
40 41
      return this.renderGraphQLErrors(e.errors);
41 42
    }
42 43
44 +
    if (e.network) {
45 +
      return this.renderNetworkError();
46 +
    }
47 +
43 48
    return (
44 49
      <ErrorView
45 50
        title={e.message}
@@ -60,6 +65,10 @@
Loading
60 65
    );
61 66
  }
62 67
68 +
  renderNetworkError() {
69 +
    return <OfflineView retry={this.props.retry} />;
70 +
  }
71 +
63 72
  render401() {
64 73
    return (
65 74
      <div className="github-GithubLoginView-Container">

@@ -24,6 +24,9 @@
Loading
24 24
25 25
    // only fetch summaries when we specify a summariesRenderer
26 26
    summariesRenderer: PropTypes.func,
27 +
28 +
    // Report errors during refetch
29 +
    reportRelayError: PropTypes.func.isRequired,
27 30
  }
28 31
29 32
  constructor(props) {
@@ -76,8 +79,12 @@
Loading
76 79
      commentCursor: null,
77 80
    },
78 81
    null,
79 -
    () => {
80 -
      this.emitter.emit('did-refetch');
82 +
    err => {
83 +
      if (err) {
84 +
        this.props.reportRelayError('Unable to refresh reviews', err);
85 +
      } else {
86 +
        this.emitter.emit('did-refetch');
87 +
      }
81 88
      callback();
82 89
    },
83 90
    {force: true},

@@ -85,7 +85,7 @@
Loading
85 85
    openReviews: PropTypes.func.isRequired,
86 86
    switchToIssueish: PropTypes.func.isRequired,
87 87
    destroy: PropTypes.func.isRequired,
88 -
    reportMutationErrors: PropTypes.func.isRequired,
88 +
    reportRelayError: PropTypes.func.isRequired,
89 89
90 90
    // Item context
91 91
    itemType: ItemTypePropType.isRequired,
@@ -171,7 +171,7 @@
Loading
171 171
            <EmojiReactionsController
172 172
              reactable={pullRequest}
173 173
              tooltips={this.props.tooltips}
174 -
              reportMutationErrors={this.props.reportMutationErrors}
174 +
              reportRelayError={this.props.reportRelayError}
175 175
            />
176 176
            <PullRequestTimelineController
177 177
              onBranch={onBranch}
@@ -344,7 +344,10 @@
Loading
344 344
      timelineCursor: null,
345 345
      commitCount: PAGE_SIZE,
346 346
      commitCursor: null,
347 -
    }, null, () => {
347 +
    }, null, err => {
348 +
      if (err) {
349 +
        this.props.reportRelayError('Unable to refresh pull request details', err);
350 +
      }
348 351
      this.setState({refreshing: false});
349 352
    }, {force: true});
350 353
  }

@@ -289,6 +289,7 @@
Loading
289 289
        commands={this.props.commandRegistry}
290 290
        localRepository={this.props.repository}
291 291
        loginModel={this.props.loginModel}
292 +
        reportRelayError={this.reportRelayError}
292 293
      />
293 294
    );
294 295
  }
@@ -441,7 +442,7 @@
Loading
441 442
              tooltips={this.props.tooltips}
442 443
              config={this.props.config}
443 444
444 -
              reportMutationErrors={this.reportMutationErrors}
445 +
              reportRelayError={this.reportRelayError}
445 446
            />
446 447
          )}
447 448
        </PaneItem>
@@ -462,7 +463,7 @@
Loading
462 463
              tooltips={this.props.tooltips}
463 464
              config={this.props.config}
464 465
              commands={this.props.commandRegistry}
465 -
              reportMutationErrors={this.reportMutationErrors}
466 +
              reportRelayError={this.reportRelayError}
466 467
            />
467 468
          )}
468 469
        </PaneItem>
@@ -925,13 +926,22 @@
Loading
925 926
    return await Promise.all(editorPromises);
926 927
  }
927 928
928 -
  reportMutationErrors = (friendlyMessage, err) => {
929 +
  reportRelayError = (friendlyMessage, err) => {
929 930
    const opts = {dismissable: true};
930 931
931 -
    if (err.errors) {
932 +
    if (err.network) {
933 +
      // Offline
934 +
      opts.icon = 'alignment-unalign';
935 +
      opts.description = "It looks like you're offline right now.";
936 +
    } else if (err.responseText) {
937 +
      // Transient error like a 500 from the API
938 +
      opts.description = 'The GitHub API reported a problem.';
939 +
      opts.detail = err.responseText;
940 +
    } else if (err.errors) {
941 +
      // GraphQL errors
932 942
      opts.detail = err.errors.map(e => e.message).join('\n');
933 -
    } else if (err.stack) {
934 -
      opts.stack = err.stack;
943 +
    } else {
944 +
      opts.detail = err.stack;
935 945
    }
936 946
937 947
    this.props.notificationManager.addError(friendlyMessage, opts);

@@ -124,7 +124,7 @@
Loading
124 124
      return null;
125 125
    }
126 126
    const token = await loginModel.getToken(loginAccount);
127 -
    if (token === UNAUTHENTICATED || token === INSUFFICIENT) {
127 +
    if (token === UNAUTHENTICATED || token === INSUFFICIENT || token instanceof Error) {
128 128
      return null;
129 129
    }
130 130
    return token;
@@ -176,6 +176,7 @@
Loading
176 176
        after: cursor,
177 177
      });
178 178
179 +
      /* istanbul ignore if */
179 180
      if (response.errors && response.errors.length > 1) {
180 181
        // eslint-disable-next-line no-console
181 182
        console.error(`Error fetching mentionable users:\n${response.errors.map(e => e.message).join('\n')}`);
Files Coverage
lib 92.68%
Project Totals (213 files) 92.68%
1
coverage:
2
  status:
3
    project:
4
      default:
5
        # Allow coverage to drop by as much as 2% from the parent commit or pull request base
6
        threshold: 2
7

8
    patch:
9
      default:
10
        threshold: 2
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading