diff --git a/cypress/fixtures/build_canceled.json b/cypress/fixtures/build_canceled.json new file mode 100644 index 000000000..86b7609cc --- /dev/null +++ b/cypress/fixtures/build_canceled.json @@ -0,0 +1,27 @@ +{ + "id": 6, + "repo_id": 1, + "number": 6, + "parent": 1, + "event": "push", + "status": "canceled", + "error": "build canceled in favor of build 7", + "enqueued": 1572980376, + "created": 1572980376, + "started": 1572980375, + "finished": 1572980375, + "deploy": "", + "clone": "https://github.com/github/octocat.git", + "source": "https://github.com/github/octocat/commit/9b1d8bded6e992ab660eaee527c5e3232d0a2441", + "title": "push received from https://github.com/github/octocat", + "message": "fixing docker params", + "commit": "9b1d8bded6e992ab660eaee527c5e3232d0a2441", + "sender": "CookieCat", + "author": "CookieCat", + "branch": "infra", + "ref": "refs/heads/infra", + "base_ref": "", + "host": "", + "runtime": "docker", + "distribution": "linux" +} diff --git a/cypress/integration/build.spec.js b/cypress/integration/build.spec.js index b32ec54ad..d8cf41281 100644 --- a/cypress/integration/build.spec.js +++ b/cypress/integration/build.spec.js @@ -295,5 +295,33 @@ context('Build', () => { cy.get('[data-test=build-error]').contains('failure authenticating'); }); }); + + context('visit canceled build', () => { + beforeEach(() => { + cy.visit('/github/octocat/6'); + cy.get('[data-test=full-build]').as('build'); + cy.get('@build').get('[data-test=build-status]').as('buildStatus'); + }); + + it('build should have canceled style', () => { + cy.get('@buildStatus').should('have.class', '-canceled'); + }); + + it('build error should show', () => { + cy.get('[data-test=build-error]').should('be.visible'); + }); + + it('build error should contain error', () => { + cy.get('[data-test=build-error]').contains('msg:'); + cy.get('[data-test=build-error]').contains( + 'build canceled in favor of build 7', + ); + }); + + it('clicking superseding build link should direct to new build page', () => { + cy.get('[data-test=new-build-link]').click({ force: true }); + cy.location('pathname').should('eq', '/github/octocat/7'); + }); + }); }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 8528937e0..4ac1ed16c 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -62,6 +62,7 @@ Cypress.Commands.add('stubBuild', () => { cy.fixture('build_success.json').as('successBuild'); cy.fixture('build_failure.json').as('failureBuild'); cy.fixture('build_error.json').as('errorBuild'); + cy.fixture('build_canceled.json').as('cancelBuild'); cy.route({ method: 'GET', url: 'api/v1/repos/*/*/builds/1', @@ -92,6 +93,18 @@ Cypress.Commands.add('stubBuild', () => { status: 200, response: '@errorBuild', }); + cy.route({ + method: 'GET', + url: 'api/v1/repos/*/*/builds/6', + status: 200, + response: '@cancelBuild', + }); + cy.route({ + method: 'GET', + url: 'api/v1/repos/*/*/builds/7', + status: 200, + response: '@successBuild', + }); }); Cypress.Commands.add('stubBuilds', () => { diff --git a/src/elm/Pages/Build/View.elm b/src/elm/Pages/Build/View.elm index 578151569..4c0072b1d 100644 --- a/src/elm/Pages/Build/View.elm +++ b/src/elm/Pages/Build/View.elm @@ -1115,8 +1115,48 @@ viewError build = ] Vela.Canceled -> - div [ class "error", Util.testAttribute "build-canceled" ] - [ text "build was canceled" + let + message = + if String.isEmpty build.error then + text "no error message" + + else + let + tgtBuild = + String.split " " build.error + |> List.Extra.last + |> Maybe.withDefault "" + in + -- check if the last part of the error message was a digit + -- to handle auto canceled build messages which come in the + -- form of "build was auto canceled in favor of build 42" + case String.toInt tgtBuild of + -- not an auto cancel message, use the returned error msg + Nothing -> + text build.error + + -- some special treatment to turn build number + -- into a link to the respective build + Just _ -> + let + linkList = + String.split "/" build.link + |> List.reverse + + newLink = + linkList + |> List.Extra.setAt 0 tgtBuild + |> List.reverse + |> String.join "/" + + msg = + String.replace tgtBuild "" build.error + in + span [] [ text msg, a [ href newLink, Util.testAttribute "new-build-link" ] [ text tgtBuild ] ] + in + div [ class "error", Util.testAttribute "build-error" ] + [ span [ class "label" ] [ text "msg:" ] + , span [ class "message" ] [ message ] ] _ -> @@ -1164,7 +1204,7 @@ statusToClass status = class "-failure" Vela.Canceled -> - class "-failure" + class "-canceled" Vela.Error -> class "-error" diff --git a/src/elm/SvgBuilder.elm b/src/elm/SvgBuilder.elm index e5e1ba03a..0c5b35dec 100644 --- a/src/elm/SvgBuilder.elm +++ b/src/elm/SvgBuilder.elm @@ -127,6 +127,41 @@ buildFailure = ] +{-| buildCanceled : produces svg icon for build status - canceled +-} +buildCanceled : Html msg +buildCanceled = + svg + [ class "build-icon -canceled" + , strokeWidth "2" + , viewBox "0 0 44 44" + , width "44" + , height "44" + , ariaHidden + ] + [ Svg.path [ d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] + , Svg.path [ d "M15 15L29 29" ] [] + ] + + +{-| buildError : produces svg icon for build status - error +-} +buildError : Html msg +buildError = + svg + [ class "build-icon -error" + , strokeWidth "2" + , viewBox "0 0 44 44" + , width "44" + , height "44" + , ariaHidden + ] + [ Svg.path [ d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" ] [] + , Svg.path [ d "M22 14V25" ] [] + , Svg.path [ d "M22 30V27" ] [] + ] + + {-| buildStatusAnimation : takes dashes as particles an svg meant to parallax scroll on a running build -} buildStatusAnimation : String -> String -> List String -> Html msg @@ -250,6 +285,61 @@ stepFailure = ] +{-| stepError : produces svg icon for step status - error +-} +stepError : Html msg +stepError = + svg + [ class "-icon -error" + , strokeWidth "2" + , viewBox "0 0 44 44" + , width "32" + , height "32" + , ariaHidden + ] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" + ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M22 13V25" + ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M22 29V32" + ] + [] + ] + + +{-| stepCanceled : produces svg icon for step status - canceled +-} +stepCanceled : Html msg +stepCanceled = + svg + [ class "-icon -canceled" + , strokeWidth "2" + , viewBox "0 0 44 44" + , width "32" + , height "32" + , ariaHidden + ] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" + ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M15 15l14 14" + ] + [] + ] + + {-| stepSkipped : produces svg icon for step status - killed Note: killed/skipped are the same thing. -} @@ -341,8 +431,50 @@ hookSkipped = , height "20" , ariaHidden ] - [ Svg.path [ attribute "vector-effect" "non-scaling-stroke", d "M15 20.1l6.923 6.9L42 5" ] [] - , Svg.path [ attribute "vector-effect" "non-scaling-stroke", d "M43 22v16.333A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1h25.666" ] [] + [ Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M5.667 1h32.666A4.668 4.668 0 0143 5.667v32.666A4.668 4.668 0 0138.333 43H5.667A4.668 4.668 0 011 38.333V5.667A4.668 4.668 0 015.667 1z" + ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke", d "M30.88 16.987l-9.744-5.625-9.747 9.383" ] + [] + , Svg.path + [ attribute "vector-effect" "non-scaling-stroke" + , d "M33 14l1 6h-6z" + , Svg.Attributes.fill "var(--color-lavender)" + ] + [] + , Svg.rect + [ attribute "vector-effect" "non-scaling-stroke" + , Svg.Attributes.fill "var(--color-lavender)" + , Svg.Attributes.x "10" + , Svg.Attributes.y "28" + , Svg.Attributes.width "4" + , Svg.Attributes.height "4" + , Svg.Attributes.rx "3" + ] + [] + , Svg.rect + [ attribute "vector-effect" "non-scaling-stroke" + , Svg.Attributes.fill "var(--color-lavender)" + , Svg.Attributes.x "20" + , Svg.Attributes.y "28" + , Svg.Attributes.width "4" + , Svg.Attributes.height "4" + , Svg.Attributes.rx "3" + ] + [] + , Svg.rect + [ attribute "vector-effect" "non-scaling-stroke" + , Svg.Attributes.fill "var(--color-lavender)" + , Svg.Attributes.x "30" + , Svg.Attributes.y "28" + , Svg.Attributes.width "4" + , Svg.Attributes.height "4" + , Svg.Attributes.rx "3" + ] + [] ] @@ -418,6 +550,36 @@ buildHistoryFailure _ = [ Svg.path [ d "M8 8l12 12M20 8L8 20" ] [] ] +{-| buildHistoryError : produces svg icon for build history status - error +-} +buildHistoryError : Int -> Html msg +buildHistoryError _ = + svg + [ class "-icon -error" + , strokeWidth "2" + , viewBox "0 0 28 28" + , width "26" + , height "26" + ] + [ Svg.path [ d "M14 8v7" ] [] + , Svg.path [ d "M14 18v2" ] [] + ] + + +{-| buildHistoryError : produces svg icon for build history status - error +-} +buildHistoryCanceled : Int -> Html msg +buildHistoryCanceled _ = + svg + [ class "-icon -canceled" + , strokeWidth "2" + , viewBox "0 0 28 28" + , width "26" + , height "26" + ] + [ Svg.path [ d "M8 8l12 12" ] [] ] + + {-| buildStatusToIcon : takes build status and returns Icon from SvgBuilder -} buildStatusToIcon : Status -> Html msg @@ -439,10 +601,10 @@ buildStatusToIcon status = buildFailure Vela.Canceled -> - buildFailure + buildCanceled Vela.Error -> - buildFailure + buildError {-| recentBuildStatusToIcon : takes build status string and returns Icon from SvgBuilder @@ -466,10 +628,10 @@ recentBuildStatusToIcon status index = buildHistoryFailure index Vela.Canceled -> - buildHistoryFailure index + buildHistoryCanceled index Vela.Error -> - buildHistoryFailure index + buildHistoryError index {-| stepStatusToIcon : takes build status and returns Icon from SvgBuilder @@ -493,10 +655,10 @@ stepStatusToIcon status = stepSkipped Vela.Canceled -> - stepFailure + stepCanceled Vela.Error -> - stepFailure + stepError {-| hookStatusToIcon : takes hook status string and returns Icon from SvgBuilder diff --git a/src/elm/Vela.elm b/src/elm/Vela.elm index bffbad0b8..ac226b032 100644 --- a/src/elm/Vela.elm +++ b/src/elm/Vela.elm @@ -1686,7 +1686,7 @@ statusToFavicon status = "-failure" Canceled -> - "-failure" + "-canceled" Error -> "-failure" diff --git a/src/scss/_main.scss b/src/scss/_main.scss index 1585ee9e7..6dbe3b364 100644 --- a/src/scss/_main.scss +++ b/src/scss/_main.scss @@ -569,6 +569,10 @@ details.build-toggle { background: var(--color-red); } +.build .status.-canceled { + background: var(--color-cyan-dark); +} + .build .info { position: relative; @@ -600,10 +604,14 @@ details.build-toggle { .build .error { max-width: 80%; - color: var(--color-red-light); font-size: 16px; } +/* canceled build messages aren't errors */ +.build:not(.-canceled) .error { + color: var(--color-red-light); +} + .build .error .message { margin-left: 0.2em; } @@ -760,6 +768,11 @@ details.build-toggle { border-bottom: 2px solid var(--color-red); } +.build.-canceled { + border-top: 2px solid var(--color-cyan-dark); + border-bottom: 2px solid var(--color-cyan-dark); +} + .-animation-dashes-1 { stroke-dasharray: 20 220 5 360; } @@ -826,6 +839,10 @@ details.build-toggle { background-color: var(--color-red); } + &.-canceled { + background-color: var(--color-cyan-dark); + } + &.-success { background-color: var(--color-green); } @@ -1296,6 +1313,10 @@ details.build-toggle { stroke: var(--color-red); } + &.-canceled { + stroke: var(--color-cyan-dark); + } + &.-pending { fill: var(--color-bg-light); stroke: var(--color-bg-light); diff --git a/src/scss/_table.scss b/src/scss/_table.scss index 19615e648..99be950f3 100644 --- a/src/scss/_table.scss +++ b/src/scss/_table.scss @@ -106,7 +106,7 @@ td .key .list-item { color: var(--color-text); border-bottom: 1px solid var(--color-bg-light); - border-left: 2px solid var(--color-cyan); + border-left: 2px solid var(--color-lavender); td { padding-top: 0; @@ -120,7 +120,7 @@ td .key .list-item { .table-base tr.-skipped { border-bottom: none; - border-left: 2px solid var(--color-cyan); + border-left: 2px solid var(--color-lavender); } .table-base .error-content { @@ -144,7 +144,7 @@ td .key .list-item { } .hook-status.-skipped { - stroke: var(--color-cyan); + stroke: var(--color-lavender); } .hook-status.-failure { diff --git a/src/scss/_themes.scss b/src/scss/_themes.scss index ac996ad59..f301ff2f7 100644 --- a/src/scss/_themes.scss +++ b/src/scss/_themes.scss @@ -56,6 +56,18 @@ body.theme-light { color: var(--color-red); } + .status.-canceled { + background-color: var(--color-cyan-semi-dark); + } + + .recent-build-link .-icon.-canceled { + background-color: var(--color-cyan-semi-dark); + } + + .steps .-icon.-canceled { + stroke: var(--color-cyan-semi-dark); + } + .hooks { background: var(--color-white); } @@ -96,7 +108,7 @@ body.theme-light { border-left: none; } - .build .error { + .build:not(.-canceled) .error { color: var(--color-red); } diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index 79287a56e..edc0900cc 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -3,6 +3,7 @@ :root { // primary colors --color-cyan-dark: hsl(192, 100%, 30%); // good for text on offwhite + --color-cyan-semi-dark: hsl(192, 100%, 38%); --color-cyan: hsl(192, 100%, 50%); // good for text on coal --color-cyan-light: hsl(192, 100%, 65%); --color-lavender-dark: hsl(286, 29%, 40%); diff --git a/src/static/images/favicon-canceled.ico b/src/static/images/favicon-canceled.ico new file mode 100644 index 000000000..695b2c3e5 Binary files /dev/null and b/src/static/images/favicon-canceled.ico differ