diff --git a/cypress/fixtures/repository.json b/cypress/fixtures/repository.json index 906b8e23c..430dfd1ce 100644 --- a/cypress/fixtures/repository.json +++ b/cypress/fixtures/repository.json @@ -42,5 +42,6 @@ } }, "pipeline_type": "yaml", - "approve_build": "fork-always" + "approve_build": "fork-always", + "approval_timeout": 7 } diff --git a/cypress/fixtures/repository_updated.json b/cypress/fixtures/repository_updated.json index 6df2cb576..51bfe405c 100644 --- a/cypress/fixtures/repository_updated.json +++ b/cypress/fixtures/repository_updated.json @@ -42,5 +42,6 @@ } }, "pipeline_type": "yaml", - "approve_build": "fork-no-write" + "approve_build": "fork-no-write", + "approval_timeout": 10 } diff --git a/cypress/integration/repo_settings.spec.js b/cypress/integration/repo_settings.spec.js index bd9e434e9..62108639f 100644 --- a/cypress/integration/repo_settings.spec.js +++ b/cypress/integration/repo_settings.spec.js @@ -35,6 +35,10 @@ context('Repo Settings', () => { cy.login('/github/octocat/settings'); }); + it('approval timeout should show', () => { + cy.get('[data-test=repo-approval-timeout]').should('be.visible'); + }); + it('build limit input should show', () => { cy.get('[data-test=repo-limit]').should('be.visible'); }); @@ -89,6 +93,45 @@ context('Repo Settings', () => { cy.get('@pipelineTypeRadio').should('have.checked'); }); + it('approval timeout input should allow number input', () => { + cy.get('[data-test=repo-approval-timeout]').as('repoApprovalTimeout'); + cy.get('[data-test=repo-approval-timeout] input').as( + 'repoApprovalTimeoutInput', + ); + cy.get('@repoApprovalTimeoutInput') + .should('be.visible') + .type('{selectall}123'); + cy.get('@repoApprovalTimeoutInput').should('have.value', '123'); + }); + + it('approval timeout input should not allow letter/character input', () => { + cy.get('[data-test=repo-approval-timeout]').as('repoApprovalTimeout'); + cy.get('[data-test=repo-approval-timeout] input').as( + 'repoApprovalTimeoutInput', + ); + cy.get('@repoApprovalTimeoutInput') + .should('be.visible') + .type('{selectall}cat'); + cy.get('@repoApprovalTimeoutInput').should('not.have.value', 'cat'); + cy.get('@repoApprovalTimeoutInput').type('{selectall}12cat34'); + cy.get('@repoApprovalTimeoutInput').should('have.value', '1234'); + }); + + it('clicking update on approval timeout should update timeout and hide button', () => { + cy.get('[data-test=repo-approval-timeout]').as('repoApprovalTimeout'); + cy.get('[data-test=repo-approval-timeout] input').as( + 'repoApprovalTimeoutInput', + ); + cy.get('@repoApprovalTimeoutInput').should('be.visible').clear(); + cy.get('@repoApprovalTimeoutInput').type('{selectall}80'); + cy.get('[data-test=repo-approval-timeout] + button') + .should('be.visible') + .click({ force: true }); + cy.get('[data-test=repo-approval-timeout] + button').should( + 'be.disabled', + ); + }); + it('build limit input should allow number input', () => { cy.get('[data-test=repo-limit]').as('repoLimit'); cy.get('[data-test=repo-limit] input').as('repoLimitInput'); diff --git a/docker-compose.yml b/docker-compose.yml index 49117557c..6bb820ee7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: # https://go-vela.github.io/docs/administration/server/ server: container_name: server - image: server:local + image: target/vela-server:latest networks: - vela environment: diff --git a/src/elm/Pages/Org_/Repo_/Settings.elm b/src/elm/Pages/Org_/Repo_/Settings.elm index 09f9e6af4..c40544fed 100644 --- a/src/elm/Pages/Org_/Repo_/Settings.elm +++ b/src/elm/Pages/Org_/Repo_/Settings.elm @@ -109,6 +109,7 @@ toLayout user route model = -} type alias Model = { repo : WebData Vela.Repository + , inApprovalTimeout : Maybe Int , inLimit : Maybe Int , inCounter : Maybe Int , inTimeout : Maybe Int @@ -120,6 +121,7 @@ type alias Model = init : Shared.Model -> Route { org : String, repo : String } -> () -> ( Model, Effect Msg ) init shared route () = ( { repo = RemoteData.Loading + , inApprovalTimeout = Nothing , inLimit = Nothing , inCounter = Nothing , inTimeout = Nothing @@ -156,6 +158,8 @@ type Msg | AllowEventsUpdate { allowEvents : Vela.AllowEvents, event : Vela.AllowEventsField } Bool | AccessUpdate String | ForkPolicyUpdate String + | ApprovalTimeoutOnInput String + | ApprovalTimeoutUpdate Int | BuildLimitOnInput String | BuildLimitUpdate Int | BuildTimeoutOnInput String @@ -552,6 +556,34 @@ update shared route msg model = } ) + ApprovalTimeoutOnInput val -> + ( { model + | inApprovalTimeout = Just <| Maybe.withDefault 0 <| String.toInt val + } + , Effect.none + ) + + ApprovalTimeoutUpdate val -> + let + payload = + { defaultRepoPayload + | approval_timeout = Just val + } + + body = + Http.jsonBody <| Vela.encodeRepoPayload payload + in + ( model + , Effect.updateRepo + { baseUrl = shared.velaAPIBaseURL + , session = shared.session + , onResponse = UpdateRepoResponse { field = Vela.ApprovalTimeout } + , org = route.params.org + , repo = route.params.repo + , body = body + } + ) + BuildLimitOnInput val -> ( { model | inLimit = Just <| Maybe.withDefault 0 <| String.toInt val @@ -721,6 +753,7 @@ view shared route model = [ viewAllowEvents shared repo AllowEventsUpdate , viewAccess repo AccessUpdate , viewForkPolicy repo ForkPolicyUpdate + , viewApprovalTimeout repo model.inApprovalTimeout ApprovalTimeoutUpdate ApprovalTimeoutOnInput , viewLimit shared repo model.inLimit BuildLimitUpdate BuildLimitOnInput , viewTimeout repo model.inTimeout BuildTimeoutUpdate BuildTimeoutOnInput , viewBuildCounter repo model.inCounter BuildCounterUpdate BuildCounterOnInput @@ -834,6 +867,79 @@ viewForkPolicy repo msg = ] +{-| viewApprovalTimeout : takes model and repo and renders the settings category for updating repo build approval timeout. +-} +viewApprovalTimeout : Vela.Repository -> Maybe Int -> (Int -> msg) -> (String -> msg) -> Html msg +viewApprovalTimeout repo inApprovalTimeout clickMsg inputMsg = + section [ class "settings", Util.testAttribute "repo-settings-approval-timeout" ] + [ h2 [ class "settings-title" ] [ text "Approval Timeout" ] + , p [ class "settings-description" ] [ text "Number of days before builds pending approval are marked as error and discarded. Discarded builds must be restarted to approve." ] + , div [ class "form-controls" ] + [ viewApprovalTimeoutInput repo inApprovalTimeout inputMsg + , viewUpdateApprovalTimeout repo inApprovalTimeout <| clickMsg <| Maybe.withDefault 0 inApprovalTimeout + ] + ] + + +{-| viewApprovalTimeoutInput : takes repo, user input, and button action and renders the text input for updating build approval timeout. +-} +viewApprovalTimeoutInput : Vela.Repository -> Maybe Int -> (String -> msg) -> Html msg +viewApprovalTimeoutInput repo inApprovalTimeout inputMsg = + div [ class "form-control", Util.testAttribute "repo-approval-timeout" ] + [ input + [ id <| "repo-approval-timeout" + , onInput inputMsg + , type_ "number" + , Html.Attributes.min "1" + , Html.Attributes.max "60" + , value <| String.fromInt <| Maybe.withDefault repo.approval_timeout inApprovalTimeout + ] + [] + , label [ class "form-label", for "repo-approval-timeout" ] [ text "days" ] + ] + + +{-| viewUpdateApprovalTimeout : takes maybe int of user entered approval timeout and current repo approval timeout and renders the button to submit the update. +-} +viewUpdateApprovalTimeout : Vela.Repository -> Maybe Int -> msg -> Html msg +viewUpdateApprovalTimeout repo inApprovalTimeout msg = + case inApprovalTimeout of + Just _ -> + button + [ classList + [ ( "button", True ) + , ( "-outline", True ) + ] + , onClick msg + , disabled <| not <| validApprovalTimeout 60 inApprovalTimeout repo <| Just repo.approval_timeout + ] + [ text "update" ] + + _ -> + text "" + + +{-| validApprovalTimeout : takes maybe string of user entered approval timeout and returns whether or not it is a valid update. +-} +validApprovalTimeout : Int -> Maybe Int -> Vela.Repository -> Maybe Int -> Bool +validApprovalTimeout maxApprovalTimeout inApprovalTimeout _ repoApprovalTimeout = + case inApprovalTimeout of + Just t -> + if t >= 1 && t <= maxApprovalTimeout then + case repoApprovalTimeout of + Just ti -> + t /= ti + + Nothing -> + True + + else + False + + Nothing -> + False + + {-| viewLimit : takes model and repo and renders the settings category for updating repo build limit. -} viewLimit : Shared.Model -> Vela.Repository -> Maybe Int -> (Int -> msg) -> (String -> msg) -> Html msg diff --git a/src/elm/Vela.elm b/src/elm/Vela.elm index 6957265d0..6c32ff712 100644 --- a/src/elm/Vela.elm +++ b/src/elm/Vela.elm @@ -462,6 +462,7 @@ type alias Repository = , allowEvents : AllowEvents , enabled : Enabled , pipeline_type : String + , approval_timeout : Int } @@ -487,6 +488,7 @@ emptyRepository = , allowEvents = defaultAllowEvents , enabled = Disabled , pipeline_type = "" + , approval_timeout = 0 } @@ -514,6 +516,7 @@ decodeRepository = -- "enabled" |> optional "active" enabledDecoder Disabled |> optional "pipeline_type" string "" + |> optional "approval_timeout" int 0 decodeRepositories : Decoder (List Repository) @@ -536,6 +539,7 @@ type alias RepoPayload = , timeout : Maybe Int , counter : Maybe Int , pipeline_type : Maybe String + , approval_timeout : Maybe Int } @@ -552,6 +556,7 @@ encodeRepoPayload repo = , ( "timeout", encodeOptional Json.Encode.int repo.timeout ) , ( "counter", encodeOptional Json.Encode.int repo.counter ) , ( "pipeline_type", encodeOptional Json.Encode.string repo.pipeline_type ) + , ( "approval_timeout", encodeOptional Json.Encode.int repo.approval_timeout ) ] @@ -567,6 +572,7 @@ defaultRepoPayload = , timeout = Nothing , counter = Nothing , pipeline_type = Nothing + , approval_timeout = Nothing } @@ -580,6 +586,7 @@ type RepoFieldUpdate | Timeout | Counter | PipelineType + | ApprovalTimeout type alias RepoFieldUpdateResponseConfig = @@ -718,6 +725,12 @@ repoFieldUpdateToResponseConfig field = "$ pipeline syntax type set to '" ++ repo.pipeline_type ++ "'." } + ApprovalTimeout -> + { successAlert = + \repo -> + "$ build approval timeout set to " ++ String.fromInt repo.approval_timeout ++ " day(s)." + } + -- ALLOW EVENTS