Handle invalid issues (#18111)
* Handle invalid issues - When you hover over a issue reference, and the issue doesn't exist, it will just hang on the loading animation. - This patch fixes that by showing them the pop-up with a "Error occured" message. * Add I18N * refactor * fix comment for lint * fix unit test for i18n * fix unit test for i18n * add comments Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
d2fac636d1
commit
e4e3df6c66
@ -207,7 +207,7 @@ func TestAPIGetReleaseByTag(t *testing.T) {
|
|||||||
|
|
||||||
var err *api.APIError
|
var err *api.APIError
|
||||||
DecodeJSON(t, resp, &err)
|
DecodeJSON(t, resp, &err)
|
||||||
assert.EqualValues(t, "Not Found", err.Message)
|
assert.NotEmpty(t, err.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIDeleteReleaseByTagName(t *testing.T) {
|
func TestAPIDeleteReleaseByTagName(t *testing.T) {
|
||||||
|
@ -141,7 +141,7 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
|
|||||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
"Bad Request: Invalid CSRF token",
|
"Bad Request: invalid CSRF token",
|
||||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ type APIContext struct {
|
|||||||
Org *APIOrganization
|
Org *APIOrganization
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently, we have the following common fields in error response:
|
||||||
|
// * message: the message for end users (it shouldn't be used for error type detection)
|
||||||
|
// if we need to indicate some errors, we should introduce some new fields like ErrorCode or ErrorType
|
||||||
|
// * url: the swagger document URL
|
||||||
|
|
||||||
// APIError is error format response
|
// APIError is error format response
|
||||||
// swagger:response error
|
// swagger:response error
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
@ -47,8 +52,8 @@ type APIValidationError struct {
|
|||||||
// APIInvalidTopicsError is error format response to invalid topics
|
// APIInvalidTopicsError is error format response to invalid topics
|
||||||
// swagger:response invalidTopicsError
|
// swagger:response invalidTopicsError
|
||||||
type APIInvalidTopicsError struct {
|
type APIInvalidTopicsError struct {
|
||||||
Topics []string `json:"invalidTopics"`
|
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
InvalidTopics []string `json:"invalidTopics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//APIEmpty is an empty response
|
//APIEmpty is an empty response
|
||||||
@ -122,9 +127,9 @@ func (ctx *APIContext) InternalServerError(err error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
type apiContextKeyType struct{}
|
||||||
apiContextKey interface{} = "default_api_context"
|
|
||||||
)
|
var apiContextKey = apiContextKeyType{}
|
||||||
|
|
||||||
// WithAPIContext set up api context in request
|
// WithAPIContext set up api context in request
|
||||||
func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
|
func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request {
|
||||||
@ -351,7 +356,7 @@ func ReferencesGitRepo(allowEmpty bool) func(http.Handler) http.Handler {
|
|||||||
// NotFound handles 404s for APIContext
|
// NotFound handles 404s for APIContext
|
||||||
// String will replace message, errors will be added to a slice
|
// String will replace message, errors will be added to a slice
|
||||||
func (ctx *APIContext) NotFound(objs ...interface{}) {
|
func (ctx *APIContext) NotFound(objs ...interface{}) {
|
||||||
var message = "Not Found"
|
var message = ctx.Tr("error.not_found")
|
||||||
var errors []string
|
var errors []string
|
||||||
for _, obj := range objs {
|
for _, obj := range objs {
|
||||||
// Ignore nil
|
// Ignore nil
|
||||||
@ -368,7 +373,7 @@ func (ctx *APIContext) NotFound(objs ...interface{}) {
|
|||||||
|
|
||||||
ctx.JSON(http.StatusNotFound, map[string]interface{}{
|
ctx.JSON(http.StatusNotFound, map[string]interface{}{
|
||||||
"message": message,
|
"message": message,
|
||||||
"documentation_url": setting.API.SwaggerURL,
|
"url": setting.API.SwaggerURL,
|
||||||
"errors": errors,
|
"errors": errors,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -104,10 +104,12 @@ error404 = The page you are trying to reach either <strong>does not exist</stron
|
|||||||
never = Never
|
never = Never
|
||||||
|
|
||||||
[error]
|
[error]
|
||||||
occurred = An error has occurred
|
occurred = An error occurred
|
||||||
report_message = If you are sure this is a Gitea bug, please search for issue on <a href="https://github.com/go-gitea/gitea/issues">GitHub</a> and open new issue if necessary.
|
report_message = If you are sure this is a Gitea bug, please search for issues on <a href="https://github.com/go-gitea/gitea/issues" target="_blank">GitHub</a> or open a new issue if necessary.
|
||||||
missing_csrf = Bad Request: no CSRF token present
|
missing_csrf = Bad Request: no CSRF token present
|
||||||
invalid_csrf = Bad Request: Invalid CSRF token
|
invalid_csrf = Bad Request: invalid CSRF token
|
||||||
|
not_found = The target couldn't be found.
|
||||||
|
network_error = Network error
|
||||||
|
|
||||||
[startpage]
|
[startpage]
|
||||||
app_desc = A painless, self-hosted Git service
|
app_desc = A painless, self-hosted Git service
|
||||||
|
@ -46,10 +46,13 @@
|
|||||||
]).values()),
|
]).values()),
|
||||||
{{end}}
|
{{end}}
|
||||||
mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
|
mermaidMaxSourceCharacters: {{MermaidMaxSourceCharacters}},
|
||||||
|
{{/* this global i18n object should only contain gereral texts. for specalized texts, it should be provied inside the related modules by: (1) API response (2) HTML data-attribute (3) PageData */}}
|
||||||
i18n: {
|
i18n: {
|
||||||
copy_success: '{{.i18n.Tr "copy_success"}}',
|
copy_success: '{{.i18n.Tr "copy_success"}}',
|
||||||
copy_error: '{{.i18n.Tr "copy_error"}}',
|
copy_error: '{{.i18n.Tr "copy_error"}}',
|
||||||
}
|
error_occurred: '{{.i18n.Tr "error.occurred"}}',
|
||||||
|
network_error: '{{.i18n.Tr "error.network_error"}}',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
{{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
|
{{/* in case some pages don't render the pageData, we make sure it is an object to prevent null access */}}
|
||||||
window.config.pageData = window.config.pageData || {};
|
window.config.pageData = window.config.pageData || {};
|
||||||
|
@ -16,13 +16,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!loading && issue === null">
|
||||||
|
<p><small>{{ i18nErrorOccurred }}</small></p>
|
||||||
|
<p>{{ i18nErrorMessage }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {SvgIcon} from '../svg.js';
|
import {SvgIcon} from '../svg.js';
|
||||||
|
|
||||||
const {appSubUrl} = window.config;
|
const {appSubUrl, i18n} = window.config;
|
||||||
|
|
||||||
// NOTE: see models/issue_label.go for similar implementation
|
// NOTE: see models/issue_label.go for similar implementation
|
||||||
const srgbToLinear = (color) => {
|
const srgbToLinear = (color) => {
|
||||||
@ -49,7 +53,9 @@ export default {
|
|||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
loading: false,
|
loading: false,
|
||||||
issue: null
|
issue: null,
|
||||||
|
i18nErrorOccurred: i18n.error_occurred,
|
||||||
|
i18nErrorMessage: null,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@ -112,14 +118,20 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
load(data, callback) {
|
load(data, callback) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
$.get(`${appSubUrl}/api/v1/repos/${data.owner}/${data.repo}/issues/${data.index}`, (issue) => {
|
this.i18nErrorMessage = null;
|
||||||
|
$.get(`${appSubUrl}/api/v1/repos/${data.owner}/${data.repo}/issues/${data.index}`).done((issue) => {
|
||||||
this.issue = issue;
|
this.issue = issue;
|
||||||
this.loading = false;
|
}).fail((jqXHR) => {
|
||||||
this.$nextTick(() => {
|
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
|
||||||
if (callback) {
|
this.i18nErrorMessage = jqXHR.responseJSON.message;
|
||||||
callback();
|
} else {
|
||||||
|
this.i18nErrorMessage = i18n.network_error;
|
||||||
|
}
|
||||||
|
}).always(() => {
|
||||||
|
this.loading = false;
|
||||||
|
if (callback) {
|
||||||
|
this.$nextTick(callback);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user