diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56573152..96cceeb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,11 @@ jobs: TEST_DATABASE: ${{ secrets.Test_Database }} run: | ./run-docker.sh + - uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: robot_logs + path: ./end_to_end_tests/logs - name: Stop End-to-End tests env: # Env variable from Github Secrets TEST_DATABASE: ${{ secrets.Test_Database }} diff --git a/.gitignore b/.gitignore index 156567c1..a31c3081 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ test/* .vscode/ robot_test/logs/ *.env -*.pyc \ No newline at end of file +*.pyc + +xeno diff --git a/README.md b/README.md index cef9cd99..815781a8 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Epimetheus offers a dashboard/UI to visualize data from [TestArchiver](https://g ### Requirements -1) PostgreSQL database with archived result data -2) `Python v3+` -3) `Node.js v10+` + 1. PostgreSQL database with archived result data + 2. `Python v3+` + 3. `Node.js v10+` ### Database @@ -19,7 +19,7 @@ Currently the only supported database engine is PostgreSQL. It can be local or c ### Backend If you have multiple python installations on your machine, remember to use the 'python3' postfix, -also with pip you might need to use 'pip3' to make sure the requirements get installed with the right python. +also with pip you might need to use 'pip3' to make sure the requirements get installed with the right python. ``` cd backend-server diff --git a/backend_server/server.py b/backend_server/server.py index f544374e..4f2f7228 100644 --- a/backend_server/server.py +++ b/backend_server/server.py @@ -16,7 +16,7 @@ def load_config_file(file_name): with open(file_name, 'r') as file: return json.load(file) - +VERSION_NUMBER = "1.0.0" @register_swagger_model class SeriesModel: @@ -202,7 +202,7 @@ def __init__(self, database): setup_swagger(handlers, swagger_url="/data/doc", description='Project repo at https://github.com/salabs/Epimetheus', - api_version='0.3.0', + api_version=VERSION_NUMBER, title='Epimetheus backend API') tornado.web.Application.__init__(self, handlers, **settings) diff --git a/backend_server/sql_queries.py b/backend_server/sql_queries.py index 0d0a7040..31dd4200 100644 --- a/backend_server/sql_queries.py +++ b/backend_server/sql_queries.py @@ -172,7 +172,7 @@ def status_counts(series, start_from, last, offset=0): count(nullif(status !~ '^FAIL', true)) as suites_failed, count(nullif(status !~ '^SKIP', true)) as suites_skipped, count(nullif(status ~ '^((SKIP)|(PASS)|(FAIL))', true)) as suites_other, - build_start_time, + min(build_start_time) as build_start_time, build_id, build_number FROM ( @@ -211,7 +211,7 @@ def status_counts(series, start_from, last, offset=0): ) AS status_per_test GROUP BY suite_id, build_number, build_id ) AS status_per_suite -GROUP BY build_number, build_id, build_start_time +GROUP BY build_number, build_id ORDER BY build_number DESC """.format(series=int(series), # nosec test_run_ids=test_run_ids(series, start_from=start_from, last=last, offset=offset)) diff --git a/docker-compose-robot-tests.yml b/docker-compose-robot-tests.yml index 70d83410..b033885b 100644 --- a/docker-compose-robot-tests.yml +++ b/docker-compose-robot-tests.yml @@ -48,6 +48,15 @@ services: dockerfile: ./resources/Dockerfile env_file: - ${ENV_FILE} + environment: + CI_PIPELINE_ID: ${GITHUB_WORKFLOW} + CI_COMMIT_REF_NAME: ${GITHUB_HEAD_REF} + CI_COMMIT_SHA: ${GITHUB_SHA} + CI_REPOSITORY: ${GITHUB_REPOSITORY} + CI_RUN_NUMBER: ${GITHUB_RUN_NUMBER} + CI_RUN_ID: ${GITHUB_RUN_ID} + CI_USERNAME: ${GITHUB_ACTOR} + CI_EVENT_NAME: ${GITHUB_EVENT_NAME} volumes: - ./end_to_end_tests/logs:/home/robot/test/logs depends_on: diff --git a/end_to_end_tests/resources/Dockerfile b/end_to_end_tests/resources/Dockerfile index 1848262e..6bf750d5 100644 --- a/end_to_end_tests/resources/Dockerfile +++ b/end_to_end_tests/resources/Dockerfile @@ -13,7 +13,7 @@ ADD variables.py . RUN pip install -r requirements.txt -RUN pip install -i https://test.pypi.org/simple/ testarchiver==0.0.4 +RUN pip install testarchiver==2.0.0 RUN chown -R robot:robot /home/robot RUN mkdir /home/robot/test/logs diff --git a/end_to_end_tests/resources/Libraries/Helper.py b/end_to_end_tests/resources/Libraries/Helper.py index 82685bd1..ef31ba86 100644 --- a/end_to_end_tests/resources/Libraries/Helper.py +++ b/end_to_end_tests/resources/Libraries/Helper.py @@ -60,3 +60,18 @@ def set_number(self, number): @keyword def get_number(self): return self.value + + @keyword + def get_build_info(self, series, build): + r = requests.get(self.host+"data/series/" + series + "/builds/" + build + "/info?") + return r.json()["series"] + + @staticmethod + @keyword + def get_team_from_info(info): + return(info["team"]) + + @staticmethod + @keyword + def get_series_from_info(info): + return(info["name"]) \ No newline at end of file diff --git a/end_to_end_tests/resources/general_keywords/breadcrumbs.robot b/end_to_end_tests/resources/general_keywords/breadcrumbs.robot new file mode 100644 index 00000000..f9bcda96 --- /dev/null +++ b/end_to_end_tests/resources/general_keywords/breadcrumbs.robot @@ -0,0 +1,77 @@ +***Settings*** +Resource ../resource.robot + +*** Keywords *** + +Navigate to series overview page using breadcrumbs + Wait Until Element is Enabled ${series_breadcrumb} + Click Element ${series_breadcrumb} + +Navigate to team page using breadcrumbs + Wait Until Element is Enabled ${team_breadcrumb} + Click Element ${team_breadcrumb} + +Navigate to build overview page using breadcrumbs + Wait Until Element is Enabled ${build_breadcrumb} + Click Element ${build_breadcrumb} + +Team page breadcrumb should contain correct value + [Arguments] ${team} + Wait Until Element Is Enabled ${team_breadcrumb} + Wait Until Element Does Not Contain ${team_breadcrumb} '' + Element Should Contain ${team_breadcrumb} ${team} + +Series page breadcrumbs should contain correct values + [Arguments] ${series} ${team} + Wait Until Element Is Enabled ${series_breadcrumb} + Team page breadcrumb should contain correct value ${team} + Element Should Contain ${series_breadcrumb} ${series} + +Build page breadcrumbs should contain correct values + [Arguments] ${build} ${series} ${team} + Wait Until Element Is Enabled ${build_breadcrumb} + Series page breadcrumbs should contain correct values ${series} ${team} + Element Should Contain ${build_breadcrumb} ${build} + +Suite page breadcrumbs should contain correct values + [Arguments] ${suite} ${build} ${series} ${team} + Wait Until Element Is Enabled ${suite_breadcrumb} + Build page breadcrumbs should contain correct values ${build} ${series} ${team} + Element Should Contain ${suite_breadcrumb} ${suite} + +Series overview page should be of stored series + [Arguments] ${series} + Wait Until Element Is Enabled ${timeline_locator} + Element Should Contain ${siteHeader} ${series} + Element Should Contain ${series_breadcrumb} ${series} + +Build overview page should be of stored build + [Arguments] ${build} + Wait Until Element Is Enabled ${buildOverviewContainer} + Element Should Contain ${build_identifier} ${build} + Element Should Contain ${siteHeader} ${build} + Element Should contain ${build_breadcrumb} ${build} + +Team page should be of stored team + [Arguments] ${team} + Location should contain ${team} + +Store team and series of series history page + Wait Until Element Is Enabled ${lastRunInfo} + ${temp_stored_team}= Get Text ${team_identifier} + ${temp_stored_series}= Get Text ${series_identifier} + + Set Suite Variable ${stored_team} ${temp_stored_team} + Set Suite Variable ${stored_series} ${temp_stored_series} + +Store the team, series and build of build page + Store team and series of series history page + Wait Until Element Is Enabled ${lastRunInfo} + ${temp_stored_build}= Get Text ${build_identifier} + Set Suite Variable ${stored_build} ${temp_stored_build} + +Store the team, series, build and suite of a suite page + Wait Until Element Is Enabled ${suite_id_locator} + Store the team, series and build of build page + ${temp_stored_suite}= Get Text ${suite_id_locator} + Set Suite Variable ${stored_suite} ${temp_stored_suite} \ No newline at end of file diff --git a/end_to_end_tests/resources/general_keywords/navigation.robot b/end_to_end_tests/resources/general_keywords/navigation.robot index 15a3d54a..4bee696e 100644 --- a/end_to_end_tests/resources/general_keywords/navigation.robot +++ b/end_to_end_tests/resources/general_keywords/navigation.robot @@ -1,16 +1,77 @@ *** Keywords *** -Current Page Is +Current Page Is [Arguments] ${url} Go To ${url} Page contains id [Arguments] ${id} - Element should be Enabled ${id} - -Id contains - [Arguments] ${id} ${1} ${2} ${3} - Element Should Contain ${id} ${1} - Element Should Contain ${id} ${2} - Element Should Contain ${id} ${3} - ${page} = Log Location \ No newline at end of file + Element should be Enabled ${id} + +Open Team Page + Go To url=${team_url} + + +Open Index Page + Go To url=${URL} + +Open Series Page of Team + [Arguments] ${team} + ${url} = Catenate SEPARATOR= ${team_url} ${team} + Go To ${url} + +Open a build + [Arguments] ${series} ${build} + ${str}= Catenate SEPARATOR= ${url} series/ ${series} /build/ ${build} /history + Set Suite Variable ${navigated_series} ${series} + Set Suite Variable ${navigated_build} ${build} + Go To ${str} + Wait Until Element is Enabled ${lastRunInfo} + + +Open history page of series + [Arguments] ${series} + ${str}= Catenate SEPARATOR= ${history_url} ${series} /history + Current Page Is url=${str} + ${url}= Get Location + Set Suite Variable ${navigated_series} ${series} + Should be equal as Strings ${url} ${str} + Wait Until Element is Enabled ${lastRunInfo} + + +Open overview page of series + [Arguments] ${series} + ${str}= Catenate SEPARATOR= ${history_url} ${series} /overview + Current Page Is url=${str} + ${url}= Get Location + Set Suite Variable ${navigated_series} ${series} + Should be equal as Strings ${url} ${str} + Wait Until Element is Enabled ${timeline_locator} + + +Open overview page of build + [Arguments] ${series} ${build} + ${url}= Catenate SEPARATOR= ${url} series/ ${series} /build/ ${build} /overview + Set Suite Variable ${navigated_series} ${series} + Set Suite Variable ${navigated_build} ${build} + Go To ${url} + Wait Until Element is Enabled ${buildOverviewContainer} + +Open a suite + [Arguments] ${series} ${build} ${suite} + ${url}= Catenate SEPARATOR= ${url} series/ ${series} /build/ ${build} /suite/ ${suite} /history + Set Suite Variable ${navigated_series} ${series} + Set Suite Variable ${navigated_build} ${build} + Set Suite Variable ${navigated_suite} ${suite} + Go To ${url} + Wait Until Element is Enabled ${lastRunInfo} + + +Navigate to first suite of build + [Arguments] ${series} ${build} + ${url}= Catenate SEPARATOR= ${url} series/ ${series} /build/ ${build} /history + Go To ${url} + Wait Until Element is Enabled ${first_suite} + Click Element ${first_suite} + Wait Until Element is Enabled ${suite_id_locator} + Wait Until Element is Enabled ${lastRunInfo} diff --git a/end_to_end_tests/resources/page_locators/build_overview.robot b/end_to_end_tests/resources/page_locators/build_overview.robot new file mode 100644 index 00000000..23ee2818 --- /dev/null +++ b/end_to_end_tests/resources/page_locators/build_overview.robot @@ -0,0 +1,2 @@ +***Variables*** +${buildOverviewContainer} //*[@id="buildGraphDiv"] \ No newline at end of file diff --git a/end_to_end_tests/resources/page_locators/build_page.robot b/end_to_end_tests/resources/page_locators/build_page.robot index 1afc15e6..b117d971 100644 --- a/end_to_end_tests/resources/page_locators/build_page.robot +++ b/end_to_end_tests/resources/page_locators/build_page.robot @@ -1,10 +1,11 @@ ***Variables*** -${fail_checkbox_locator} //*[@id="last-run-checkbox-container"]/label[1]/input -${pass_checkbox_locator} //*[@id="last-run-checkbox-container"]/label[2]/input -${table_locator} //*[@id="last-run-table"]/tbody -${table_row_locator} //*[@id="last-run-table"]/tbody/tr +${fail_checkbox_locator} xpath://*[@id="last-run-checkbox-container"]/label[1]/span +${pass_checkbox_locator} xpath://*[@id="last-run-checkbox-container"]/label[2]/span +${table_locator} xpath://*[@id="last-run-table"]/tbody +${table_row_locator} xpath://*[@id="last-run-table"]/tbody/tr +${pass_span} xpath://*[@id="last-run-table"]/tbody/tr[*]/td[*]/span[text()='Pass'] +${fail_span} xpath://*[@id="last-run-table"]/tbody/tr[*]/td[*]/span[text()='Fail'] +${last_run_table} xpath://*[@id="last-run-table"] -${pass_span} //*[@id="last-run-table"]/tbody/tr[*]/td[*]/span[text()='Pass'] -${fail_span} //*[@id="last-run-table"]/tbody/tr[*]/td[*]/span[text()='Fail'] -${last_run_table} //*[@id="last-run-table"] +${first_suite} xpath://*[@id="last-run-table"]/tbody/tr[1]/td[1]/a diff --git a/end_to_end_tests/resources/page_locators/help_page.robot b/end_to_end_tests/resources/page_locators/help_page.robot index e13cca65..1ec36e55 100644 --- a/end_to_end_tests/resources/page_locators/help_page.robot +++ b/end_to_end_tests/resources/page_locators/help_page.robot @@ -1,2 +1,2 @@ ***Variables*** -${HELP_PAGE_WELCOME_ELEMENT} //*[@id="frontpage"]/h1 \ No newline at end of file +${HELP_PAGE_WELCOME_ELEMENT} xpath://*[@id="frontpage"]/h1 \ No newline at end of file diff --git a/end_to_end_tests/resources/page_locators/history_page.robot b/end_to_end_tests/resources/page_locators/history_page.robot index fd96cb71..6410dbc0 100644 --- a/end_to_end_tests/resources/page_locators/history_page.robot +++ b/end_to_end_tests/resources/page_locators/history_page.robot @@ -1,2 +1,22 @@ ***Variables*** -${table_header_xpath} //*[@id="history-table-head"]/tr/th \ No newline at end of file +${table_header_xpath} xpath://*[@id="history-table-head"]/tr/th +${series_history_dropdown} xpath://*[@id="build_amount_dropdown"] + +${history_build_selector_5} xpath://*[@id="react-select-2-option-0"] +${history_build_selector_10} xpath://*[@id="react-select-2-option-1"] +${history_build_selector_15} xpath://*[@id="react-select-2-option-2"] +${history_build_selector_30} xpath://*[@id="react-select-2-option-3"] + + +${offset_latest} xpath://*[@id="latest_offset_button"] +${offset_field} xpath://*[@id="offset_field"] +${offset_left} xpath://*[@id="left_offset_button"] +${offset_right} xpath://*[@id="right_offset_button"] +${series_history_most_recent} //*[@id="history-table-head"]/tr/th[3]/a +${series_history_second_recent} //*[@id="history-table-head"]/tr/th[4]/a +${series_history_third_recent} //*[@id="history-table-head"]/tr/th[5]/a + +${enabled_offset_right} xpath://*[contains(@class, 'rightfalse')] +${enabled_offset_left} xpath://*[contains(@class, 'leftfalse')] +${disabled_offset_right} xpath://*[contains(@class, 'rightrue')] +${disabled_offset_left} xpath://*[contains(@class, 'leftrue')] \ No newline at end of file diff --git a/end_to_end_tests/resources/page_locators/nav_bar.robot b/end_to_end_tests/resources/page_locators/nav_bar.robot index ec81bb10..ca801417 100644 --- a/end_to_end_tests/resources/page_locators/nav_bar.robot +++ b/end_to_end_tests/resources/page_locators/nav_bar.robot @@ -1,4 +1,8 @@ ***Variables*** -${NAV_HOMEPAGE} xpath://*[@id="main-nav"]/ul/li[1]/a -${NAV_HISTORY} xpath://*[@id="main-nav"]/ul/li[2]/a -${NAV_GITHUB} xpath://*[@id="main-nav"]/ul/li[3]/a \ No newline at end of file +${NAV_HOMEPAGE} xpath://*[@id="main-nav"]/div/a[1] +${NAV_TEAM} xpath://*[@id="main-nav"]/div/a[2] +${NAV_GITHUB} xpath://*[@id="main-nav"]/div/a[3] + + +${INNER_HISTORY} xpath://*[@id="main-nav"]/ul/div/li[1]/a +${INNER_DASHBOARD} xpath://*[@id="main-nav"]/ul/div/li[2]/a diff --git a/end_to_end_tests/resources/page_locators/overview.robot b/end_to_end_tests/resources/page_locators/overview.robot new file mode 100644 index 00000000..f4fa4ff7 --- /dev/null +++ b/end_to_end_tests/resources/page_locators/overview.robot @@ -0,0 +1,3 @@ +***Variables*** +${timeline_locator} xpath://*[@id="timeLineContainer"] +${siteHeader} xpath://*[@id="siteHeader"] \ No newline at end of file diff --git a/end_to_end_tests/resources/page_locators/series_page.robot b/end_to_end_tests/resources/page_locators/series_page.robot index 600d962d..7aadc352 100644 --- a/end_to_end_tests/resources/page_locators/series_page.robot +++ b/end_to_end_tests/resources/page_locators/series_page.robot @@ -1,2 +1,5 @@ -***Variable*** -${series_xpath} //*[@id="selectedTeam"]/div[2]/div[1]/div[2]/div[1]/h4 \ No newline at end of file +***Variables*** +${series_xpath} xpath://*[@id="selectedTeam"]/div[2]/div[1]/div[2]/div[1]/h4 +${series_list} xpath://*[@id="selectedTeam"]/div[2]/div[*] +${team_breadcrumb} xpath://*[@id="TeamBreadCrumb"] +${series_click_elements} xpath://*[@id="selectedTeam"]/div[2]/div[*]/div[2]/div[1] \ No newline at end of file diff --git a/end_to_end_tests/resources/page_locators/shared_resources.robot b/end_to_end_tests/resources/page_locators/shared_resources.robot new file mode 100644 index 00000000..d4cefb62 --- /dev/null +++ b/end_to_end_tests/resources/page_locators/shared_resources.robot @@ -0,0 +1,9 @@ +***Variables** +${team_breadcrumb} xpath://*[@id="TeamBreadCrumb"] +${series_breadcrumb} xpath://*[@id="SeriesBreadCrumb"] +${build_breadcrumb} xpath://*[@id="BuildBreadCrumb"] + +${team_identifier} xpath://*[@id="lastRunInfo"]/p[1]/span[2] +${series_identifier} xpath://*[@id="lastRunInfo"]/p[2]/span[2] +${build_identifier} xpath://*[@id="lastRunInfo"]/p[3]/span[2] +${lastRunInfo} xpath://*[@id="lastRunInfo"]/p \ No newline at end of file diff --git a/end_to_end_tests/resources/page_locators/suite_page.robot b/end_to_end_tests/resources/page_locators/suite_page.robot new file mode 100644 index 00000000..ae1c9caf --- /dev/null +++ b/end_to_end_tests/resources/page_locators/suite_page.robot @@ -0,0 +1,3 @@ +***Variables*** +${suite_breadcrumb} xpath://*[@id="SuiteBreadCrumb"] +${suite_id_locator} xpath://*[@id="datatable"]/div[2]/span[2] diff --git a/end_to_end_tests/resources/page_locators/team_page.robot b/end_to_end_tests/resources/page_locators/team_page.robot index 2b39a9b1..7428210f 100644 --- a/end_to_end_tests/resources/page_locators/team_page.robot +++ b/end_to_end_tests/resources/page_locators/team_page.robot @@ -1,3 +1,3 @@ ***Variables*** -${team_xpath} //*[@id="team"]/div/div/h3 -${teams_xpath} //*[@id="team"]/div/div \ No newline at end of file +${team_xpath} xpath://*[@id="team"]/div/div/h3 +${teams_xpath} xpath://*[@id="team"]/div/div[*] diff --git a/end_to_end_tests/resources/resource.robot b/end_to_end_tests/resources/resource.robot index bb4a58e6..c0efe425 100644 --- a/end_to_end_tests/resources/resource.robot +++ b/end_to_end_tests/resources/resource.robot @@ -7,5 +7,10 @@ Resource page_locators/help_page.robot Resource page_locators/series_page.robot Resource page_locators/team_page.robot Resource page_locators/build_page.robot +Resource page_locators/overview.robot +Resource page_locators/build_overview.robot +Resource page_locators/suite_page.robot +Resource page_locators/shared_resources.robot Resource general_keywords/initialisation_keywords.robot Resource general_keywords/navigation.robot +Resource general_keywords/breadcrumbs.robot diff --git a/end_to_end_tests/robot_tests/__init__.robot b/end_to_end_tests/robot_tests/__init__.robot index 2a7abaef..44a677e0 100644 --- a/end_to_end_tests/robot_tests/__init__.robot +++ b/end_to_end_tests/robot_tests/__init__.robot @@ -1,5 +1,5 @@ *** Settings *** -Resource ${EXECDIR}${/}resources${/}resource.robot +Resource ${EXECDIR}/resources/resource.robot Suite Setup Open Browser To Epimetheus Landing Page ${BROWSER} ${remote_url} Suite Teardown Custom Teardown diff --git a/end_to_end_tests/robot_tests/backend/api/__init__.robot b/end_to_end_tests/robot_tests/backend/api/__init__.robot new file mode 100644 index 00000000..de5657f7 --- /dev/null +++ b/end_to_end_tests/robot_tests/backend/api/__init__.robot @@ -0,0 +1,3 @@ + +***Settings*** +Force Tags Backend diff --git a/end_to_end_tests/robot_tests/frontend/__init__.robot b/end_to_end_tests/robot_tests/frontend/__init__.robot new file mode 100644 index 00000000..7f4a3046 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/__init__.robot @@ -0,0 +1,3 @@ + +***Settings*** +Force Tags Frontend diff --git a/end_to_end_tests/robot_tests/frontend/build_overview_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/build_overview_tests/__init__.robot new file mode 100644 index 00000000..6f16bd6c --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/build_overview_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags BuildOverview \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/build_overview_tests/build_overview_breadcrums.robot b/end_to_end_tests/robot_tests/frontend/build_overview_tests/build_overview_breadcrums.robot new file mode 100644 index 00000000..8321b68a --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/build_overview_tests/build_overview_breadcrums.robot @@ -0,0 +1,16 @@ +***Settings*** +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Verify Build Overview Breadcrumbs + Open overview page of build 1 1 + Store the team, series and build of build page + Build page breadcrumbs should contain correct values ${stored_build} ${stored_series} ${stored_team} + Navigate to series overview page using breadcrumbs + Series overview page should be of stored series ${stored_series} + Series page breadcrumbs should contain correct values ${stored_series} ${stored_team} + Open overview page of build 1 1 + Navigate to team page using breadcrumbs + Team page should be of stored team ${stored_team} + Team page breadcrumb should contain correct value ${stored_team} \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/build_page_tests/Clickboxes.robot b/end_to_end_tests/robot_tests/frontend/build_page_tests/Clickboxes.robot similarity index 80% rename from end_to_end_tests/robot_tests/build_page_tests/Clickboxes.robot rename to end_to_end_tests/robot_tests/frontend/build_page_tests/Clickboxes.robot index 7ec60254..fcc5083e 100644 --- a/end_to_end_tests/robot_tests/build_page_tests/Clickboxes.robot +++ b/end_to_end_tests/robot_tests/frontend/build_page_tests/Clickboxes.robot @@ -1,5 +1,5 @@ ***Settings*** -Resource ../../resources/resource.robot +Resource ../../../resources/resource.robot *** Test Cases *** @@ -17,17 +17,11 @@ Test CheckBoxes *** Keywords *** -Open a build - [Arguments] ${series} ${build} - ${str}= Catenate SEPARATOR= ${url} series/ ${series} /build/ ${build} /history - Go To ${str} - Hide Tests [Arguments] ${status} ${path}= Set Variable If '${status}' == 'Failing' ${fail_checkbox_locator} ${pass_checkbox_locator} Wait Until Element Is Enabled ${fail_checkbox_locator} - Checkbox Should Not Be Selected ${Path} - Select Checkbox ${path} + Click Element ${path} Build Should have test executions Wait Until Element Is Enabled ${last_run_table} @@ -40,8 +34,7 @@ Show Tests [Arguments] ${status} ${path}= Set Variable If '${status}' == 'Failing' ${fail_checkbox_locator} ${pass_checkbox_locator} Wait Until Element Is Enabled ${fail_checkbox_locator} - Checkbox Should Be Selected ${path} - Unselect Checkbox ${path} + Click Element ${path} The status of all tests should be [Arguments] ${status} diff --git a/end_to_end_tests/robot_tests/frontend/build_page_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/build_page_tests/__init__.robot new file mode 100644 index 00000000..c73a6f8f --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/build_page_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags Build \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/build_page_tests/build_page_breadcrumbs.robot b/end_to_end_tests/robot_tests/frontend/build_page_tests/build_page_breadcrumbs.robot new file mode 100644 index 00000000..9c757eb8 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/build_page_tests/build_page_breadcrumbs.robot @@ -0,0 +1,16 @@ +***Settings*** +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Test Build History Breadcrumbs + Open a build 1 1 + Store the team, series and build of build page + Build page breadcrumbs should contain correct values ${stored_build} ${stored_series} ${stored_team} + Navigate to series overview page using breadcrumbs + Series overview page should be of stored series ${stored_series} + Series page breadcrumbs should contain correct values ${stored_series} ${stored_team} + Open a build 1 1 + Navigate to team page using breadcrumbs + Team page should be of stored team ${stored_team} + Team page breadcrumb should contain correct value ${stored_team} diff --git a/end_to_end_tests/robot_tests/frontend/help_page_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/help_page_tests/__init__.robot new file mode 100644 index 00000000..b9517d5f --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/help_page_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags Help \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/history_page_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/history_page_tests/__init__.robot new file mode 100644 index 00000000..56223875 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/history_page_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags History \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/history_page_tests/history_page_breadcrumbs.robot b/end_to_end_tests/robot_tests/frontend/history_page_tests/history_page_breadcrumbs.robot new file mode 100644 index 00000000..d93cc491 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/history_page_tests/history_page_breadcrumbs.robot @@ -0,0 +1,12 @@ +***Settings*** +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Test Series History Breadcrumbs + Open history page of series 1 + Store team and series of series history page + Series page breadcrumbs should contain correct values ${stored_series} ${stored_team} + Navigate to team page using breadcrumbs + Team page should be of stored team ${stored_team} + Team page breadcrumb should contain correct value ${stored_team} \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/history_page_tests/test_buttons.robot b/end_to_end_tests/robot_tests/frontend/history_page_tests/test_buttons.robot new file mode 100644 index 00000000..648457fc --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/history_page_tests/test_buttons.robot @@ -0,0 +1,123 @@ +***Settings*** + +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Test Build Amount Dropdown + Open history page of series 3 + Select From Dropdown ${history_build_selector_5} + Table should be limited to number=5 + Select From Dropdown ${history_build_selector_10} + Table should be limited to number=10 + Select From Dropdown ${history_build_selector_30} + Table should be limited to number=30 + +Test Offset Functionality + Open history page of series 3 + Store Most Recent Builds + Offset should be 0 + Most Recent Build Number should be ${most_recent_build} + Set Offset field to 1000 + Left Button Should be disabled + Right Button Should be disabled + Set Offset field to 1 + Right Button Should be enabled + Click Right Button + Offset should be 1 + Most Recent Build Number should be ${second_recent_build} + Click Right Button + Offset should be 2 + Most Recent Build Number should be ${third_recent_build} + Set Offset field to 1 + Left Button Should be enabled + Click Left Button + Offset should be 1 + Most Recent Build Number should be ${second_recent_build} + Left Button Should be enabled + Click Left Button + Offset should be 0 + Most Recent Build Number should be ${most_recent_build} + +#Test Offset Functionality with Offset URL, to be added + +*** Keywords *** + +Select From Dropdown + [Arguments] ${dropdown_option} + Wait Until Element Is Enabled ${series_history_dropdown} + Click Element ${series_history_dropdown} + Wait Until Element Is Visible ${dropdown_option} + Click Element ${dropdown_option} + +Browser is on a history page of series + [Arguments] ${series} + ${str}= Catenate SEPARATOR= ${history_url} ${series} /history + Current Page Is url=${str} + ${url}= Get Location + Should be equal as Strings ${url} ${str} + +Table should be limited to + [Arguments] ${number} + Wait Until Element Is Enabled ${table_header_xpath} + ${elements} = Get Element Count ${table_header_xpath} + ${sum} = Evaluate ${number} + 2 + Should be True ${sum}>=${elements} + +Store Most Recent Builds + Wait Until Element Is Enabled ${series_history_most_recent} + ${temp_most_recent}= Get Text ${series_history_most_recent} + ${temp_second_recent}= Get Text ${series_history_second_recent} + ${temp_third_recent}= Get Text ${series_history_third_recent} + + Set Suite Variable ${most_recent_build} ${temp_most_recent} + Set Suite Variable ${second_recent_build} ${temp_second_recent} + Set Suite Variable ${third_recent_build} ${temp_third_recent} + +Most Recent Build Number should be + [Arguments] ${build} + Wait Until Element Is Enabled ${series_history_most_recent} + ${temp_most_recent}= Get Text ${series_history_most_recent} + + Should be equal as Strings ${build} ${temp_most_recent} + +Set Offset field to + [Arguments] ${text} + Input Text ${offset_field} ${text} + +Offset should be + [Arguments] ${value} + ${url}= Get Location + Run Keyword if 'offset' in "${url}" Check offset url ${value} + +Check offset url + [Arguments] ${value} + ${url}= Get Location + ${full_string}= Catenate SEPARATOR= offset = ${value} + Should Contain ${url} ${full_string} + +Left Button Should be disabled + Wait Until Element Is Visible ${offset_left} + ${count}= Get Element Count ${disabled_offset_left} + Should be equal as Integers ${count} 0 + +Left Button Should be enabled + Wait Until Element Is Enabled ${offset_left} + ${count}= Get Element Count ${enabled_offset_left} + Should be equal as Integers ${count} 1 + +Right Button Should be enabled + Wait Until Element Is Enabled ${offset_right} + ${count}= Get Element Count ${enabled_offset_right} + Should be equal as Integers ${count} 1 + +Right Button Should be disabled + Wait Until Element Is Visible ${offset_right} + ${count}= Get Element Count ${disabled_offset_right} + Should be equal as Integers ${count} 0 + +Click Right Button + Click Element ${offset_right} +Click Left Button + Click Element ${offset_left} + diff --git a/end_to_end_tests/robot_tests/history_page_tests/url_navigation.robot b/end_to_end_tests/robot_tests/frontend/history_page_tests/url_navigation.robot similarity index 63% rename from end_to_end_tests/robot_tests/history_page_tests/url_navigation.robot rename to end_to_end_tests/robot_tests/frontend/history_page_tests/url_navigation.robot index 81ad32b1..b54bd78d 100644 --- a/end_to_end_tests/robot_tests/history_page_tests/url_navigation.robot +++ b/end_to_end_tests/robot_tests/frontend/history_page_tests/url_navigation.robot @@ -1,5 +1,6 @@ ***Settings*** -Resource ../../resources/resource.robot + +Resource ../../../resources/resource.robot ***Test Cases*** @@ -7,5 +8,5 @@ Test Url Navigation Current Page Is url=${URL} Wait Until Page Contains Epimetheus Wait Until Element Contains ${HELP_PAGE_WELCOME_ELEMENT} Welcome - Click Element ${NAV_HISTORY} + Click Element ${NAV_TEAM} diff --git a/end_to_end_tests/robot_tests/frontend/navbar_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/navbar_tests/__init__.robot new file mode 100644 index 00000000..67cd8dcd --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/navbar_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags NavBar \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/navbar_tests/navbar.robot b/end_to_end_tests/robot_tests/frontend/navbar_tests/navbar.robot new file mode 100644 index 00000000..530e6745 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/navbar_tests/navbar.robot @@ -0,0 +1,110 @@ +***Settings*** + +Resource ../../../resources/resource.robot +Library Collections + +***Variable*** + +${nav_id}= 'main-nav' +@{default_nav} About Teams GitHub +@{inner_nav} History Dashboard + +***Test Cases*** + +Test NavBar Links from index + Current Page Is url=${URL} + ${url}= Get Location + Test Page without Inner Nav ${url} + +Test Navbar Links From Team Page + Open Team Page + ${url}= Get Location + Test Page without Inner Nav ${url} + +Test Navbar Links From Series Page + Open Series Page of Team Epimetheus + ${url}= Get Location + Test Page without Inner Nav ${url} + +Test Navbar Links From History Page + Open history page of series 3 + ${url}= Get Location + Test Page without Inner Nav ${url} + +Test Navbar Links From Build Page + Open a build 1 1 + ${url}= Get Location + Test Page without Inner Nav ${url} + +Test Navbar links From Dashboard Page + Open overview page of series 3 + ${url}= Get Location + Test Page without Inner Nav ${url} + +*** Keywords *** + +Test page with inner nav + [Arguments] ${return_url} + Current Page is ${return_url} + Element should be visible ${nav_id} + Default navbar contains ${default_nav} + Inner navbar contains ${inner_nav} + + Test Dashboard Nav Link + Current Page is ${return_url} + + Test History Nav Link + Current Page is ${return_url} + + Test Team Nav Link + Current Page is ${return_url} + + Test Help Nav Link + +Test page without inner nav + [Arguments] ${return_url} + Current Page is ${return_url} + Element should be visible ${nav_id} + Default navbar contains ${default_nav} + + Test Team Nav Link + Current Page Is ${return_url} + Test Team Nav Link + +Test Team Nav Link + Click Link ${NAV_TEAM} + Current Page Is url=${team_url} + Element should be visible ${nav_id} + Default navbar contains ${default_nav} + +Test Help Nav Link + Click Link ${NAV_HOMEPAGE} + Current Page Is url=${URL} + Element should be visible ${nav_id} + Default navbar contains ${default_nav} + +Test History Nav Link + Click Link ${INNER_HISTORY} + +Test Dashboard Nav Link + Click Link ${INNER_DASHBOARD} + + +Default navbar contains + [Arguments] ${navbar_content} + + ${0}= Get From List ${navbar_content} 0 + ${1}= Get From List ${navbar_content} 1 + ${2}= Get From List ${navbar_content} 2 + + Element Should Contain ${nav_id} ${0} + Element Should Contain ${nav_id} ${1} + Element Should Contain ${nav_id} ${2} + +Inner navbar contains + [Arguments] ${inner_navbar_content} + ${0}= Get From List ${inner_navbar_content} 0 + ${1}= Get From List ${inner_navbar_content} 1 + Element Should Contain ${nav_id} ${0} + Element Should Contain ${nav_id} ${1} + diff --git a/end_to_end_tests/robot_tests/frontend/series_overview_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/series_overview_tests/__init__.robot new file mode 100644 index 00000000..120799c9 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/series_overview_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags SeriesOverview \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/series_overview_tests/series_overview_breadcrumbs.robot b/end_to_end_tests/robot_tests/frontend/series_overview_tests/series_overview_breadcrumbs.robot new file mode 100644 index 00000000..da4e49f4 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/series_overview_tests/series_overview_breadcrumbs.robot @@ -0,0 +1,16 @@ +***Settings*** +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Verify Last build info and Breadcrumb + Open overview page of series 1 + ${info}= Get Build Info 1 1 + ${team}= Get Team From Info ${info} + ${series}= Get Series From Info ${info} + + Series page breadcrumbs should contain correct values ${series} ${team} + Navigate to team page using breadcrumbs + Team page should be of stored team ${team} + Team page breadcrumb should contain correct value ${team} + diff --git a/end_to_end_tests/robot_tests/frontend/series_page_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/series_page_tests/__init__.robot new file mode 100644 index 00000000..53048fe3 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/series_page_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags Series \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/series_page_tests/navigation.robot b/end_to_end_tests/robot_tests/frontend/series_page_tests/navigation.robot new file mode 100644 index 00000000..3a047cfe --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/series_page_tests/navigation.robot @@ -0,0 +1,55 @@ +***Settings*** + +Resource ../../../resources/resource.robot +Library String +Library Collections + +*** Variables *** +@{series_names} +@{last_builds} + +*** Test Cases *** + +Test Navigation to Overview Pages + Open Series Page of Team Epimetheus + Store Series Headers + Test Overview of All Stored Series + +Test Navigation to Last Builds + Open Series Page of Team Epimetheus + Store Series Headers + Test Last Builds of All Stored Series + + +*** Keywords *** + + +Store Series Headers + Wait Until Element is Enabled ${series_list} + ${series}= Get WebElements ${series_list} + + FOR ${serial} IN @{series} + + ${header_text}= Get Line ${serial.text} 0 + Append To List ${series_names} ${header_text} + END + +Test Overview of All Stored Series + Wait Until Element is Enabled ${series_list} + + FOR ${header} IN @{series_names} + Click Element //h3[.="${header}"]/ancestor-or-self::div[2]/div[2]/div[1] + Wait Until Element is Enabled ${timeline_locator} + Go Back + Wait Until Element is Enabled ${series_list} + END + + +Test Last Builds of All Stored Series + Wait Until Element is Enabled ${series_list} + FOR ${header} IN @{series_names} + Click Element //h3[.="${header}"]/ancestor-or-self::div[2]/div[2]/div[2] + Wait Until Element is Enabled ${buildOverviewContainer} + Go Back + Wait Until Element is Enabled ${series_list} + END diff --git a/end_to_end_tests/robot_tests/frontend/series_page_tests/series_amount.robot b/end_to_end_tests/robot_tests/frontend/series_page_tests/series_amount.robot new file mode 100644 index 00000000..44163dc7 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/series_page_tests/series_amount.robot @@ -0,0 +1,36 @@ +***Settings*** + +Resource ../../../resources/resource.robot + + +*** Test Cases *** + + +Series Amount + Store Amount of Series of team in API Epimetheus + Open Series Page of Team Epimetheus + Series amount displayed by UI matches API + + +*** Keywords *** + + +Store Amount of Series of team in API + [Arguments] ${team} + + ${amount}= Get Series Number ${team} + Set Suite Variable ${series_amount} ${amount} + +Open Series Page of Team + [Arguments] ${team} + ${url} = Catenate SEPARATOR= ${team_url} ${team} + Go To ${url} + +Series amount displayed by UI matches API + ${url}= Get Location + + Wait Until Element Is Enabled ${series_list} + ${elements}= Get Element Count ${series_list} + + Should Be Equal As Numbers ${elements} ${series_amount} + diff --git a/end_to_end_tests/robot_tests/frontend/series_page_tests/series_cards_breadcrumbs.robot b/end_to_end_tests/robot_tests/frontend/series_page_tests/series_cards_breadcrumbs.robot new file mode 100644 index 00000000..534ce0dd --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/series_page_tests/series_cards_breadcrumbs.robot @@ -0,0 +1,9 @@ +***Settings*** +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Test Series Selector Breadcrubs + Open Series Page of Team Epimetheus + Team page should be of stored team Epimetheus + Team page breadcrumb should contain correct value Epimetheus \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/suite_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/suite_tests/__init__.robot new file mode 100644 index 00000000..7be05059 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/suite_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags SuitePage \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/suite_tests/suite_page_breadcrumbs.robot b/end_to_end_tests/robot_tests/frontend/suite_tests/suite_page_breadcrumbs.robot new file mode 100644 index 00000000..2a4596e9 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/suite_tests/suite_page_breadcrumbs.robot @@ -0,0 +1,20 @@ +***Settings*** +Resource ../../../resources/resource.robot + +*** Test Cases *** + +Verify Last build info and Breadcrumb + Navigate to first suite of build 1 1 + Store the team, series, build and suite of a suite page + Suite page breadcrumbs should contain correct values ${stored_suite} ${stored_build} ${stored_series} ${stored_team} + Navigate to build overview page using breadcrumbs + Build page breadcrumbs should contain correct values ${stored_build} ${stored_series} ${stored_team} + Build overview page should be of stored build ${stored_build} + Open a suite 1 1 ${stored_suite} + Navigate to series overview page using breadcrumbs + Series overview page should be of stored series ${stored_series} + Series page breadcrumbs should contain correct values ${stored_series} ${stored_team} + Open a suite 1 1 ${stored_suite} + Navigate to team page using breadcrumbs + Team page should be of stored team ${stored_team} + Team page breadcrumb should contain correct value ${stored_team} diff --git a/end_to_end_tests/robot_tests/frontend/team_page_tests/__init__.robot b/end_to_end_tests/robot_tests/frontend/team_page_tests/__init__.robot new file mode 100644 index 00000000..d897e5f2 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/team_page_tests/__init__.robot @@ -0,0 +1,3 @@ +*** Settings *** + +Force Tags Team \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/frontend/team_page_tests/navigation.robot b/end_to_end_tests/robot_tests/frontend/team_page_tests/navigation.robot new file mode 100644 index 00000000..3d488734 --- /dev/null +++ b/end_to_end_tests/robot_tests/frontend/team_page_tests/navigation.robot @@ -0,0 +1,36 @@ +***Settings*** + +Resource ../../../resources/resource.robot +Library String +Library Collections + +*** Variables *** +@{team_names} + +*** Test Cases *** + +Test Teams Page Component Usage + Open Team Page + Store Team Headers + Test Team Navigation using Team Headers + +*** Keywords *** + +Store Team Headers + Wait Until Element is Enabled ${teams_xpath} + ${teams}= Get WebElements ${teams_xpath} + + FOR ${teami} IN @{teams} + ${header_text}= Get Line ${teami.text} 0 + Append To List ${team_names} ${header_text} + END + +Test Team Navigation using Team Headers + Wait Until Element is Enabled ${teams_xpath} + #This long identifier points to the header part of the component that matches the header text + FOR ${header} IN @{team_names} + Click Element //*[@id="team"]/div/div[*]/h3[contains(text(),"${header}")] + Wait Until Element is Enabled ${series_list} + Go Back + Wait Until Element is Enabled ${teams_xpath} + END \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/team_page_tests/team_amount.robot b/end_to_end_tests/robot_tests/frontend/team_page_tests/team_amount.robot similarity index 86% rename from end_to_end_tests/robot_tests/team_page_tests/team_amount.robot rename to end_to_end_tests/robot_tests/frontend/team_page_tests/team_amount.robot index 7a750ecf..273ff41b 100644 --- a/end_to_end_tests/robot_tests/team_page_tests/team_amount.robot +++ b/end_to_end_tests/robot_tests/frontend/team_page_tests/team_amount.robot @@ -1,5 +1,6 @@ ***Settings*** -Resource ../../resources/resource.robot + +Resource ../../../resources/resource.robot *** Test Cases *** diff --git a/end_to_end_tests/robot_tests/history_page_tests/test_buttons.robot b/end_to_end_tests/robot_tests/history_page_tests/test_buttons.robot deleted file mode 100644 index e71ce946..00000000 --- a/end_to_end_tests/robot_tests/history_page_tests/test_buttons.robot +++ /dev/null @@ -1,42 +0,0 @@ -***Settings*** -Resource ../../resources/resource.robot - -*** Test Cases *** - -Test Buttons - Browser is on a history page of series 3 - Check history buttons - Click Button 5 - Table should be limited to number=5 - Click Button 10 - Table should be limited to number=10 - Click Button 100 - Table should be limited to number=100 - - -*** Keywords *** - - -Browser is on a history page of series - [Arguments] ${series} - ${str}= Catenate SEPARATOR= ${history_url} ${series} /history - Current Page Is url=${str} - ${url}= Get Location - Should be equal as Strings ${url} ${str} - -Check history buttons - - Page Should Contain Button Passing - Page Should Contain Button Failing - Page Should Contain Button 5 - Page Should Contain Button 10 - Page Should Contain Button 15 - Page Should Contain Button 30 - Page Should Contain Button 100 - -Table should be limited to - [Arguments] ${number} - Wait Until Element Is Enabled ${table_header_xpath} - ${elements} = Get Element Count ${table_header_xpath} - ${sum} = Evaluate ${number} + 2 - Should be True ${sum}>=${elements} diff --git a/end_to_end_tests/robot_tests/navbar_tests/navbar.robot b/end_to_end_tests/robot_tests/navbar_tests/navbar.robot deleted file mode 100644 index 77f20b5d..00000000 --- a/end_to_end_tests/robot_tests/navbar_tests/navbar.robot +++ /dev/null @@ -1,33 +0,0 @@ -***Settings*** -Resource ../../resources/resource.robot - -***Variable*** - -${nav_id}= 'main-nav' - -***Test Cases*** - -Test NavBar Links from index - Current Page Is url=${URL} - Page contains id id=${nav_id} - Id contains id=${nav_id} 1=Help 2=Team 3=GitHub - - Click Link Team - Current Page Is url=${team_url} - Element should be visible ${nav_id} - Id contains id=${nav_id} 1=Help 2=Team 3=GitHub - - Click Link Help - Current Page Is url=${URL} - Element should be visible ${nav_id} - Id contains id=${nav_id} 1=Help 2=Team 3=GitHub - -#Test Navbar Links From Team Page - - -#Test Navbar Links From Series Page - - -#Test Navbar Links From History Page - - diff --git a/end_to_end_tests/robot_tests/series_page_tests/amount of series.robot b/end_to_end_tests/robot_tests/series_page_tests/amount of series.robot deleted file mode 100644 index 923aaee0..00000000 --- a/end_to_end_tests/robot_tests/series_page_tests/amount of series.robot +++ /dev/null @@ -1,31 +0,0 @@ -***Settings*** -Resource ../../resources/resource.robot - -*** Test Cases *** - - -Series Amount - Log Amount of Series of team in API Epimetheus - UI displays correct amount of series for Epimetheus - - -*** Keywords *** - - -Log Amount of Series of team in API - [Arguments] ${team} - - ${series}= Get Series Number ${team} - Set Number ${series} - - - -UI displays correct amount of series for - [Arguments] ${team} - ${urli} = Catenate SEPARATOR= ${team_url} ${team} - Go To ${urli} - Wait Until Element Is Enabled //*[@id="selectedTeam"]/div[2]/div - ${elements}= Get Element Count //*[@id="selectedTeam"]/div[2]/div - ${api} = Get Number - Should Be Equal As Numbers ${elements} ${api} - diff --git a/end_to_end_tests/robot_tests/team_page_tests/navigation.robot b/end_to_end_tests/robot_tests/team_page_tests/navigation.robot deleted file mode 100644 index feb630de..00000000 --- a/end_to_end_tests/robot_tests/team_page_tests/navigation.robot +++ /dev/null @@ -1,12 +0,0 @@ -***Settings*** -Resource ../../resources/resource.robot - -*** Test Cases *** - -Test Teams Page - Go To url=${team_url} - Wait Until Element is Enabled ${team_xpath} - Click Element ${team_xpath} - Wait Until Element is Enabled ${series_xpath} - Click Element ${series_xpath} - Wait Until Element is Enabled ${table_header_xpath} \ No newline at end of file diff --git a/end_to_end_tests/run-e2e-tests.sh b/end_to_end_tests/run-e2e-tests.sh index 1f04937a..9bca277b 100755 --- a/end_to_end_tests/run-e2e-tests.sh +++ b/end_to_end_tests/run-e2e-tests.sh @@ -1,21 +1,103 @@ #!/bin/sh + + +if [ ! -z "${CI_PIPELINE_ID}" ]; then + python -m robot --outputdir ./logs/ \ --variablefile variables.py \ - --metadata "version:0.1.0" \ + --metadata "version:0.3.0" \ + --metadata "ci_pipeline_id:$CI_PIPELINE_ID" \ + --metadata "branch:$CI_COMMIT_REF_NAME" \ + --metadata "commit_sha:$CI_COMMIT_SHA" \ + --metadata "job_url:https://github.com/$CI_REPOSITORY/actions/runs/$CI_RUN_ID" \ + --metadata "ci_run_id:$CI_RUN_ID" \ + --metadata "run_number:$CI_RUN_NUMBER" \ + --metadata "username:$CI_USERNAME" \ + --metadata "ci_event_name:$CI_EVENT_NAME" \ + --metadata "Environment:CI" \ + --metadata "Test_Framework:robotframework" \ + --include Backend \ ./robot_tests -EXITVAL=$? +BACKEND=$? + #Testarchiver data storing can be added here, #Important to exit with the right exit value from the test execution, #not with the exitvalue from data storing. +echo "---------------------------------------------" +echo " Archiving Backend reports from ./logs -directory" +echo "---------------------------------------------" +find ./logs -name \*.xml -type f -print0 | xargs -0 -n1 testarchiver --dbengine postgresql --database "$DATABASE" --host "$HOST" \ + --user "$USER" --pw "$PASSWORD" \ + --team Epimetheus --series ci_backend#"${CI_RUN_NUMBER}" --series "${CI_COMMIT_REF_NAME}"#"${CI_RUN_NUMBER}" --format robotframework + +python -m robot --outputdir ./logs/ \ + --variablefile variables.py \ + --metadata "version:0.3.0" \ + --metadata "ci_pipeline_id:$CI_PIPELINE_ID" \ + --metadata "branch:$CI_COMMIT_REF_NAME" \ + --metadata "commit_sha:$CI_COMMIT_SHA" \ + --metadata "job_url:https://github.com/$CI_REPOSITORY/actions/runs/$CI_RUN_ID" \ + --metadata "ci_run_id:$CI_RUN_ID" \ + --metadata "run_number:$CI_RUN_NUMBER" \ + --metadata "username:$CI_USERNAME" \ + --metadata "ci_event_name:$CI_EVENT_NAME" \ + --metadata "Environment:CI" \ + --metadata "Test_Framework:robotframework" \ + --include Frontend \ + ./robot_tests + +FRONTEND=$? + +echo "---------------------------------------------" +echo " Archiving Frontend Page reports from ./logs -directory" +echo "---------------------------------------------" +find ./logs -name \*.xml -type f -print0 | xargs -0 -n1 testarchiver --dbengine postgresql --database "$DATABASE" --host "$HOST" \ + --user "$USER" --pw "$PASSWORD" \ + --team Epimetheus --series ci_frontend#"${CI_RUN_NUMBER}" --series "${CI_COMMIT_REF_NAME}"#"${CI_RUN_NUMBER}" --format robotframework + +EXITVAL=$((FRONTEND+BACKEND)) + +else + +python -m robot --outputdir ./logs/ \ + --variablefile variables.py \ + --metadata "version:0.3.0" \ + --metadata "environment:locally" \ + --include Backend \ + ./robot_tests +BACKEND=$? + +#Testarchiver data storing can be added here, +#Important to exit with the right exit value from the test execution, +#not with the exitvalue from data storing. + +echo "---------------------------------------------" +echo " Archiving Backend reports from ./logs -directory" +echo "---------------------------------------------" +find ./logs -name \*.xml -type f -print0 | xargs -0 -n1 testarchiver --dbengine postgresql --database "$DATABASE" --host "$HOST" \ + --user "$USER" --pw "$PASSWORD" \ + --team Epimetheus --series ci_backend --format robotframework + + + +python -m robot --outputdir ./logs/ \ + --variablefile variables.py \ + --metadata "version:0.3.0" \ + --metadata "environment:locally" \ + --include Frontend \ + ./robot_tests +FRONTEND=$? echo "---------------------------------------------" -echo " Archiving reports from ./logs -directory" +echo " Archiving Frontend Page reports from ./logs -directory" echo "---------------------------------------------" find ./logs -name \*.xml -type f -print0 | xargs -0 -n1 testarchiver --dbengine postgresql --database "$DATABASE" --host "$HOST" \ --user "$USER" --pw "$PASSWORD" \ - --team Epimetheus --series e2e_test --format robotframework + --team Epimetheus --series ci_frontend --format robotframework +EXITVAL=$((FRONTEND+BACKEND)) +fi exit $EXITVAL diff --git a/end_to_end_tests/variables.py b/end_to_end_tests/variables.py index f6d95e1f..28ba1e98 100644 --- a/end_to_end_tests/variables.py +++ b/end_to_end_tests/variables.py @@ -6,14 +6,23 @@ BROWSER = 'chrome' SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 -URL = 'http://frontend-server:3000/' -#URL = 'http://localhost:3000/' + + +if 'CI_PIPELINE_ID' in os.environ: + URL = 'http://frontend-server:3000/' +else: + URL = 'http://localhost:3000/' + history_url = URL+'series/' team_url = URL+'team/' nav_id = 'main-nav' -remote_url= 'epimetheus_seleniumgrid_1:4444/wd/hub' -#remote_url='' +if 'CI_PIPELINE_ID' in os.environ: + remote_url= 'epimetheus_seleniumgrid_1:4444/wd/hub' +else: + remote_url='' dir_path = os.path.dirname(os.path.realpath(__file__)) -Backend= 'http://backend-server' -#Backend= 'http://localhost' +if 'CI_PIPELINE_ID' in os.environ: + Backend= 'http://backend-server' +else: + Backend= 'http://localhost' RESOURCES = os.path.join(dir_path, 'robot', 'resources') diff --git a/frontend/.babelrc b/frontend/.babelrc index 9cff1deb..84a20a01 100644 --- a/frontend/.babelrc +++ b/frontend/.babelrc @@ -1,3 +1,3 @@ { - "plugins": ["emotion"] + "plugins": ["babel-plugin-styled-components"] } diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 5ad5faa2..d87ab596 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -13,9 +13,10 @@ "no-console": 1, "react-hooks/rules-of-hooks": 2, "react-hooks/exhaustive-deps": 1, - "no-unused-vars": ["error", { "varsIgnorePattern": "React" }] + "no-unused-vars": ["error", { "varsIgnorePattern": "React" }], + "no-irregular-whitespace": 1 }, - "plugins": ["react", "import", "jsx-a11y", "react-hooks", "emotion"], + "plugins": ["react", "import", "jsx-a11y", "react-hooks"], "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2018, diff --git a/frontend/.gitignore b/frontend/.gitignore index 5586ff1a..f5e92e2e 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -24,5 +24,6 @@ yarn.lock npm-debug.log* yarn-debug.log* yarn-error.log* +end_to_end_tests/run-e2e-tests-python3.sh -.env \ No newline at end of file +.env diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 8421bbcf..a262ba54 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,4 +1,5 @@ { "singleQuote": true, - "semi": true + "semi": true, + "trailingComma": "es5" } diff --git a/frontend/README.md b/frontend/README.md index badac13e..002bb427 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -14,6 +14,7 @@ npm run start The default configuration for backend server is `http://localhost:5000`. Backend server configuration can be changed by setting the following environment variables: + ``` REACT_APP_SERVER_URL=localhost REACT_APP_FRONT_END_PORT=5000 @@ -44,9 +45,11 @@ Install plugins: [Prettier](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) [Eslint](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) In Visual Studio Code settings check + ``` Prettier: Require Config ``` + and ``` diff --git a/frontend/package.json b/frontend/package.json index 67fdbd38..a36849ba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,27 +1,32 @@ { "name": "frontend", - "version": "0.3.0", + "version": "1.0.0", "private": true, "dependencies": { - "@emotion/core": "^10.0.21", + "apexcharts": "^3.19.2", "i18next": "^19.4.4", "normalize.css": "^8.0.1", "prop-types": "^15.7.2", "ramda": "^0.27.0", - "react": "^16.10.2", + "react": "^16.13.1", + "react-apexcharts": "^1.3.7", "react-dom": "^16.10.2", "react-fontawesome": "^1.7.1", "react-i18next": "^11.4.0", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", - "react-scripts": "^3.3.0" + "react-scripts": "^3.4.3", + "react-select": "^3.1.0", + "react-vega": "^7.3.0", + "styled-components": "^5.1.1", + "vega": "^5.13.0", + "vega-lite": "^4.13.0" }, "devDependencies": { "babel-eslint": "^10.0.3", - "babel-plugin-emotion": "^10.0.21", + "babel-plugin-styled-components": "^1.11.1", "eslint": "^6.5.1", "eslint-config-prettier": "^6.4.0", - "eslint-plugin-emotion": "^10.0.14", "eslint-plugin-import": "^2.20.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-prettier": "^3.1.2", diff --git a/frontend/public/img/testicons.png b/frontend/public/img/testicons.png deleted file mode 100644 index 16cd525c..00000000 Binary files a/frontend/public/img/testicons.png and /dev/null differ diff --git a/frontend/src/App.js b/frontend/src/App.jsx similarity index 62% rename from frontend/src/App.js rename to frontend/src/App.jsx index 9a61a114..d403f925 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.jsx @@ -1,27 +1,27 @@ // eslint-disable-next-line -import React, { useState, useEffect, Suspense } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +import React, { useEffect, Suspense } from 'react'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; -import theme from './styles/theme'; import ThemeContext from './contexts/themeContext'; import MainContent from './components/MainContent'; import MainNav from './components/MainNav'; +import Footer from './components/Footer'; import History from './pages/History'; -import Dashboard from './pages/Dashboard'; +import Overview from './pages/Overview'; import Build from './pages/Build'; import Frontpage from './pages/Frontpage'; import Team from './pages/Team'; import Suite from './pages/Suite'; import { useStateValue } from './contexts/state'; import './utils/i118n'; - import 'normalize.css'; import './index.css'; +import theme from './styles/theme'; +import { StyledApp } from './App.styles'; const App = () => { // eslint-disable-next-line - const [{ selectedBranchState, amountOfBuilds }, dispatch] = useStateValue(); + const [{}, dispatch] = useStateValue(); + useEffect(() => { const fetchData = async () => { dispatch({ type: 'setLoadingState', loadingState: true }); @@ -31,7 +31,7 @@ const App = () => { dispatch({ type: 'setLoadingState', loadingState: false }); dispatch({ type: 'setBranches', - branches: json + branches: json, }); } catch (error) { // console.log(error); @@ -40,59 +40,11 @@ const App = () => { fetchData(); }, [dispatch]); - const appStyles = css` - display: flex; - min-height: 100vh; - width: 100%; - ${theme.testTheme.container} .login-btn { - padding: 10px; - margin-bottom: 20px; - } - a { - color: ${theme.testTheme.linkColor}; - } - a:active, - a:hover { - color: ${theme.testTheme.linkColor}; - } - a.skip-main { - left: -999px; - position: absolute; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; - z-index: -999; - } - a.skip-main:focus, - a.skip-main:active { - background-color: #fff; - left: auto; - top: auto; - width: 30%; - height: auto; - overflow: auto; - margin: 10px 35%; - padding: 5px; - border: 1px solid black; - text-align: center; - z-index: 999; - } - , - select:focus, - input:focus { - outline: 1px solid ${theme.testTheme.linkColor}; - } - `; - return ( -
+ - - Skip to main content - @@ -108,11 +60,11 @@ const App = () => { - - + + - - + + @@ -125,9 +77,10 @@ const App = () => { +
+
); }; diff --git a/frontend/src/App.styles.js b/frontend/src/App.styles.js new file mode 100644 index 00000000..46bd0c41 --- /dev/null +++ b/frontend/src/App.styles.js @@ -0,0 +1,22 @@ +import styled from 'styled-components'; +import theme from './styles/theme'; + +export const StyledApp = styled.div` + display: flex; + flex-direction: column; + min-height: 100vh; + width: 100%; + background-color: var(--nero-white); + color: #222; + p { + line-height: 1.6; + } + + a { + color: var(--titan-green); + } + select:focus, + input:focus { + outline: 1px solid ${theme.testTheme.linkColor}; + } +`; diff --git a/frontend/src/components/BranchFilter.js b/frontend/src/components/BranchFilter.js index 0522c84b..a0e7fa61 100644 --- a/frontend/src/components/BranchFilter.js +++ b/frontend/src/components/BranchFilter.js @@ -1,84 +1,86 @@ // eslint-disable-next-line import React, { useState, useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; import { useStateValue } from '../contexts/state'; import theme from '../styles/theme'; +import styled from 'styled-components'; -const BranchFilter = () => { - const filterStyles = css` - padding: 20px 40px 20px 0px; +const Container = styled.div` + padding: 20px 40px 20px 0px; + width: 50%; + min-width: 250px; +`; - width: 50%; - min-width: 250px; - input { - width: 200px; - padding: 10px 10px; - margin: 0; - border: 1px solid #333; - border-radius: 0; - -moz-appearance: none; - -webkit-appearance: none; - appearance: none; - background-size: 1.2em auto, 100%; - background-color: #fefefe; - } - .suggestion-container { - margin-left: 20px; - margin-top: 0px; - border: 1px solid #000; - border-top: 0; - border-bottom: 1; - background-color: #fafafa; - width: 200px; - max-height: 300px; - overflow: auto; - overflow-x: hidden; - z-index: 2; - position: absolute; - font-size: 14px; - div { - padding: 5px 10px; - } - } - .suggestions-error { - padding-left: 0px; - padding-top: 5px; - position: absolute; - font-size: 75%; - color: ${theme.colors.fail}; - display: none; - font-weight: bold; - &.show { - display: block; - } - } - .suggestion-item { - margin-top: 0; - border: 0; - border-top: 0; - background-color: #fafafa; - width: 200px; - padding-left: 20px; - } - .option-active { - background-color: #0030bb; - border: 0px solid blue; - color: white; - cursor: pointer; - } - `; +const Input = styled.input` + width: 200px; + padding: 10px 10px; + margin: 0; + border: 1px solid #333; + border-radius: 0; + -moz-appearance: none; + -webkit-appearance: none; + appearance: none; + background-size: 1.2em auto, 100%; + background-color: #fefefe; +`; + +const SuggestionContainer = styled.div` + margin-left: 20px; + margin-top: 0px; + border: 1px solid #000; + border-top: 0; + border-bottom: 1; + background-color: #fafafa; + width: 200px; + max-height: 300px; + overflow: auto; + overflow-x: hidden; + z-index: 2; + position: absolute; + font-size: 14px; + div { + padding: 5px 10px; + } +`; +const SuggestionError = styled.div` + padding-left: 0px; + padding-top: 5px; + position: absolute; + font-size: 75%; + color: ${theme.colors.fail}; + display: none; + font-weight: bold; + &.show { + display: block; + } +`; + +const SuggestionItem = styled.div` + margin-top: 0; + border: 0; + border-top: 0; + background-color: #fafafa; + width: 200px; + padding-left: 20px; +`; + +const ActiveSuggestionItem = styled(SuggestionItem)` + background-color: #0030bb; + border: 0px solid blue; + color: white; + cursor: pointer; +`; +const BranchFilter = () => { // Global states from reducer const [ { branchesState, selectedBranchState, amountOfBuilds, - selectedBuildState + selectedBuildState, }, - dispatch + dispatch, ] = useStateValue(); const options = branchesState; const selectedBuild = selectedBuildState.id; @@ -203,7 +205,7 @@ const BranchFilter = () => { ); return ( -
+

Series / Branch: {selectedBranchState.name}

@@ -212,7 +214,7 @@ const BranchFilter = () => { - { onMouseDown={e => handleFocusChange(e)} value={userInput} /> -
{ > {suggestions && suggestions.map(({ id, name, team }, index) => { - const cls = - index === activeOption - ? 'option-active' - : ''; - return ( -
handleClick(e)} - onMouseEnter={e => - handleSuggestionItemHover(e) - } - onMouseLeave={e => - handleSuggestionItemHover(e) - } - onKeyDown={() => handleKeyDown()} - > - {name} - {team} -
- ); + if (index === activeOption) { + return ( + handleClick(e)} + onMouseEnter={e => + handleSuggestionItemHover(e) + } + onMouseLeave={e => + handleSuggestionItemHover(e) + } + onKeyDown={() => handleKeyDown()} + > + {name} - {team} + + ); + } else { + return ( + handleClick(e)} + onMouseEnter={e => + handleSuggestionItemHover(e) + } + onMouseLeave={e => + handleSuggestionItemHover(e) + } + onKeyDown={() => handleKeyDown()} + > + {name} - {team} + + ); + } })} -
-
+ No branches matching text found -
+
- + ); }; diff --git a/frontend/src/components/BreadcrumbNav.js b/frontend/src/components/BreadcrumbNav.js deleted file mode 100644 index 80d4dc74..00000000 --- a/frontend/src/components/BreadcrumbNav.js +++ /dev/null @@ -1,88 +0,0 @@ -// eslint-disable-next-line -import React from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import { useStateValue } from '../contexts/state'; -import { useParams } from 'react-router'; -import { Link } from 'react-router-dom'; - -const BreadcrumbItem = () => { - const { name } = useParams(); - const [{ selectedBranchState }] = useStateValue(); - const teamName = name || selectedBranchState.team; - - return ( - - {teamName} - - ); -}; - -const BreadcrumbItemSeries = () => { - const { series } = useParams(); - const [{ selectedBranchState }] = useStateValue(); - const seriesId = series || selectedBranchState.id; - return ( -
- > - - {selectedBranchState.name} - -
- ); -}; - -const BreadcrumbItemBuild = () => { - const { buildId, seriesId } = useParams(); - return ( -
- > - - {buildId} - -
- ); -}; - -const BreadcrumbItemSuite = () => { - const { suiteId } = useParams(); - return ( -
- > {suiteId} -
- ); -}; -const BreadcrumbNav = ({ status }) => { - const breadCrumbNavStyles = css` - font-size: 14px; - a { - padding: 5px 5px 5px 10px; - &:hover, - &:active { - background-color: #ccc; - transition: 0.1s background-color; - } - } - .BreadCrumbSeries { - padding-left: 0; - } - div { - display: inline; - } - `; - - return ( -
-
{BREADCRUMB_STATUS[`${status}`]}
-
- ); -}; - -export default BreadcrumbNav; - -const BREADCRUMB_STATUS = { - team: , - series: , - build: , - suite: -}; diff --git a/frontend/src/components/BreadcrumbNav.jsx b/frontend/src/components/BreadcrumbNav.jsx new file mode 100644 index 00000000..787ea176 --- /dev/null +++ b/frontend/src/components/BreadcrumbNav.jsx @@ -0,0 +1,94 @@ +// eslint-disable-next-line +import React from 'react'; +import { useStateValue } from '../contexts/state'; +import { useParams } from 'react-router'; +import { + BreadcrumbContainer, + StyledInnerDiv, + StyledLink, + TeamsLink, +} from './BreadcrumbNav.styles'; + +const BreadcrumbTeams = () => { + return Teams; +}; + +const BreadcrumbItem = () => { + const { name } = useParams(); + const [{ selectedBranchState }] = useStateValue(); + const teamName = name || selectedBranchState.team; + + return ( + + / + + {teamName} + + + ); +}; + +const BreadcrumbItemSeries = () => { + const { series } = useParams(); + const [{ selectedBranchState }] = useStateValue(); + const seriesId = series || selectedBranchState.id; + return ( + + / + + {selectedBranchState.name} + + + ); +}; + +const BreadcrumbItemBuild = () => { + const { buildId, seriesId } = useParams(); + return ( + + / + + {buildId} + + + ); +}; + +const BreadcrumbItemSuite = () => { + const { suiteId } = useParams(); + return ( + + /{' '} + {suiteId} + + ); +}; + +const BREADCRUMB_STATUS = { + team: , + series: , + build: , + suite: , +}; + +const BreadcrumbNav = ({ status }) => { + return ( + + {BREADCRUMB_STATUS[`${status}`]} + + ); +}; + +export default BreadcrumbNav; diff --git a/frontend/src/components/BreadcrumbNav.styles.js b/frontend/src/components/BreadcrumbNav.styles.js new file mode 100644 index 00000000..35e771f8 --- /dev/null +++ b/frontend/src/components/BreadcrumbNav.styles.js @@ -0,0 +1,43 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +export const BreadcrumbContainer = styled.div` + font-size: 14px; + margin: 10px 0px; + + a { + text-decoration: none; + } + + .SeriesBreadCrumb { + color: ${props => props.status === 'series' && 'var(--pirlo-blue)'}; + font-weight: ${props => props.status === 'series' && 'bolder'}; + } + + .BuildBreadCrumb { + color: ${props => props.status === 'build' && 'var(--pirlo-blue)'}; + font-weight: ${props => props.status === 'build' && 'bolder'}; + } +`; + +export const StyledInnerDiv = styled.div` + display: inline; + + #SuiteBreadCrumb { + color: var(--pirlo-blue); + font-weight: bolder; + } +`; + +export const StyledLink = styled(Link)` + padding: 5px 5px 5px 10px; + &:hover, + &:active { + background-color: #ccc; + transition: 0.1s background-color; + } +`; + +export const TeamsLink = styled(StyledLink)` + padding-left: 0 !important; +`; diff --git a/frontend/src/components/Card.js b/frontend/src/components/Card.js index 921ed7b7..2fdb6523 100644 --- a/frontend/src/components/Card.js +++ b/frontend/src/components/Card.js @@ -1,45 +1,43 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +import styled from 'styled-components'; -const Card = ({ team, numberOfSeries }) => { - const [t] = useTranslation(['team']); - let history = useHistory(); +const StyledDiv = styled.div` + background-color: var(--nero-white); + box-shadow: rgba(0, 0, 0, 0.16) 0px 5px 7px, rgba(0, 0, 0, 0.23) 0px 5px 7px; + margin: 10px; + padding: 10px; + line-height: 16px; + min-height: 120px; + width: 300px; + height: 200px; + cursor: pointer; - const Mongolia = css` - background-color: var(--powder-white); - box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 4px, - rgba(0, 0, 0, 0.23) 0px 3px 4px; - margin: 10px; - padding: 10px; - line-height: 16px; - min-height: 120px; - width: 250px; - cursor: pointer; + @media only screen and (min-width: 1024px) { + width: 400px; + } +`; - span { - color: var(--pirlo-blue); - } +const StyledSpan = styled.span` + color: var(--pirlo-blue); +`; - @media only screen and (min-width: 1024px) { - width: 400px; - height: 200px; - } - `; +const Card = ({ team, numberOfSeries }) => { + const [t] = useTranslation(['team']); + let history = useHistory(); return ( -
history.push(`/team/${team}`)} role={'presentation'} >

{team}

- {t('series')}: {numberOfSeries} + {t('series')}:{' '} + {numberOfSeries}
-
+ ); }; diff --git a/frontend/src/components/Checkbox.js b/frontend/src/components/Checkbox.js index da7b5385..ae679d17 100644 --- a/frontend/src/components/Checkbox.js +++ b/frontend/src/components/Checkbox.js @@ -1,32 +1,35 @@ // eslint-disable-next-line import React, { useState } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; import { useQueryParams } from '../hooks/useQuery'; import { useHistory, useLocation } from 'react-router-dom'; +import styled from 'styled-components'; + +const CheckboxContainer = styled.div` + display: flex; + flex-direction: column; + padding: 20px 40px 20px 0px; +`; + +const StyledCheckbox = styled.input` + border: 1px solid #eee; + border-radius: 10px; + background-color: white; + padding: 5px; + margin: 5px; + cursor: pointer; +`; + +const SelectedCheckbox = styled(StyledCheckbox)` + background-color: transparent !important; + border: 2px solid #243b53 !important; + color: #243b53 !important; +`; + +const Header = styled.h3` + padding-left: 7px; +`; const Checkbox = () => { - const filterStyles = css` - display: flex; - flex-direction: column; - padding: 20px 40px 20px 0px; - input { - border: 1px solid #eee; - border-radius: 10px; - background-color: white; - padding: 5px; - margin: 5px; - cursor: pointer; - } - .selected { - background-color: transparent; - border: 2px solid #243b53; - color: #243b53; - } - h3 { - padding-left: 7px; - } - `; const history = useHistory(); const location = useLocation(); const queryParams = useQueryParams(); @@ -45,34 +48,44 @@ const Checkbox = () => { history.push({ pathname: `${location.pathname}`, search: `?${updateTags(e.target.value)}`, - state: {} + state: {}, }); }; return ( -
-

Hide tests

- handleFilterChange(e)} - /> - handleFilterChange(e)} - /> -
+ +
Hide tests
+ {queryParams.getAll('tag').includes('Passing') === true ? ( + handleFilterChange(e)} + /> + ) : ( + handleFilterChange(e)} + /> + )} + {queryParams.getAll('tag').includes('Failing') === true ? ( + handleFilterChange(e)} + /> + ) : ( + handleFilterChange(e)} + /> + )} +
); }; diff --git a/frontend/src/components/Footer.jsx b/frontend/src/components/Footer.jsx new file mode 100644 index 00000000..8b0a605a --- /dev/null +++ b/frontend/src/components/Footer.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import packageJson from '../../package.json'; +import { FooterContainer, EpiIcon, TextStyles } from './Footer.styles'; + +const Footer = () => { + const [t] = useTranslation(['mainnav']); + + return ( + + {t('footer.E')} + + {t('footer.epimetheus')} {t('footer.version')}{' '} + {packageJson.version}{' '} + {t('footer.powered')}{' '} + + {t('footer.siili')} + + . + + + ); +}; + +export default Footer; diff --git a/frontend/src/components/Footer.styles.js b/frontend/src/components/Footer.styles.js new file mode 100644 index 00000000..c89908e8 --- /dev/null +++ b/frontend/src/components/Footer.styles.js @@ -0,0 +1,32 @@ +import styled from 'styled-components'; + +export const FooterContainer = styled.div` + margin-top: auto; + background: var(--titan-green); + color: var(--nero-white); + display: flex; + align-items: center; + min-height: 50px; + + a { + background: var(--titan-green); + color: var(--nero-white) !important; + } + + .underline { + text-decoration: underline; + } +`; + +export const EpiIcon = styled.span` + margin-left: 40px; + background: var(--nero-white); + color: var(--titan-green); + border-radius: 6px; + padding: 0 7px; + font-weight: bolder; +`; + +export const TextStyles = styled.span` + margin-left: 20px; +`; diff --git a/frontend/src/components/LastRunCheckbox.js b/frontend/src/components/LastRunCheckbox.js deleted file mode 100644 index 5719c1fc..00000000 --- a/frontend/src/components/LastRunCheckbox.js +++ /dev/null @@ -1,75 +0,0 @@ -// eslint-disable-next-line -import React, { useState } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import { useStateValue } from '../contexts/state'; - -const Checkbox = () => { - // eslint-disable-next-line - const [{ lastRunFilterPass, lastRunFilterFail }, dispatch] = useStateValue(); - const [passFilter, setPassFilter] = useState(lastRunFilterPass.isChecked); - const [failFilter, setFailFilter] = useState(lastRunFilterFail.isChecked); - - const filterStyles = css` - padding: 20px 0 40px; - label { - margin-right: 20px; - display: block; - float: left; - input { - margin-left: 8px; - position: relative; - top: 1.5px; - display: inline-block; - transform: scale(1.2); - } - } - `; - - const handlePassFilterChange = e => { - dispatch({ - type: 'setLastRunFilterPass', - filterType: passFilter ? '' : e.target.value, - isChecked: !passFilter - }); - - setPassFilter(!passFilter); - }; - - const handleFailFilterChange = e => { - dispatch({ - type: 'setLastRunFilterFail', - filterType: failFilter ? '' : e.target.value, - isChecked: !failFilter - }); - - setFailFilter(!failFilter); - }; - - return ( -
- - -
- ); -}; - -export default Checkbox; diff --git a/frontend/src/components/LastRunHeading.js b/frontend/src/components/LastRunHeading.js deleted file mode 100644 index 3a6ee0f0..00000000 --- a/frontend/src/components/LastRunHeading.js +++ /dev/null @@ -1,15 +0,0 @@ -// eslint-disable-next-line -import React from 'react'; -import { useStateValue } from '../contexts/state'; - -const LastRunHeading = ({ id }) => { - const [{ selectedBranchState }] = useStateValue(); - - return ( -

- Project / {selectedBranchState.name} # {id} -

- ); -}; - -export default LastRunHeading; diff --git a/frontend/src/components/Loading.js b/frontend/src/components/Loading.js index eff04be3..bd966d6c 100644 --- a/frontend/src/components/Loading.js +++ b/frontend/src/components/Loading.js @@ -1,48 +1,46 @@ // eslint-disable-next-line import React from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +import styled from 'styled-components'; -const Loading = () => { - const loadingStyles = css` - height: 30px; - line-height: 30px; +const LoadingDiv = styled.div` + height: 30px; + line-height: 30px; + padding: 0; + &:after { + margin: 0; padding: 0; - &:after { - margin: 0; - padding: 0; - line-height: 30px; - font-size: 1rem; - content: '...'; - vertical-align: bottom; - display: inline-block; - width: 0px; - height: 30px; - animation-name: history-loader; - animation-duration: 1.5s; - animation-iteration-count: infinite; - overflow: hidden; + line-height: 30px; + font-size: 1rem; + content: '...'; + vertical-align: bottom; + display: inline-block; + width: 0px; + height: 30px; + animation-name: history-loader; + animation-duration: 1.5s; + animation-iteration-count: infinite; + overflow: hidden; + } + @keyframes history-loader { + from { + width: 0; } - @keyframes history-loader { - from { - width: 0; - } - to { - width: 140px; - } + to { + width: 140px; } - `; + } +`; +const Loading = () => { return ( -
Loading -
+ ); }; diff --git a/frontend/src/components/MainContent.js b/frontend/src/components/MainContent.js index b7f7ed97..59d41bf4 100644 --- a/frontend/src/components/MainContent.js +++ b/frontend/src/components/MainContent.js @@ -1,20 +1,19 @@ // eslint-disable-next-line import React from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +import styled from 'styled-components'; -const MainContent = ({ children }) => { - const mainStyles = css` - padding: 20px; - overflow: auto; - width: 100%; - `; +const StyledDiv = styled.div` + padding: 20px; + overflow: auto; + width: 100%; + + @media only screen and (min-width: 1024px) { + padding: 20px 40px; + } +`; - return ( -
- {children} -
- ); +const MainContent = ({ children }) => { + return {children}; }; export default MainContent; diff --git a/frontend/src/components/MainNav.js b/frontend/src/components/MainNav.js index cc37e9e0..12bc7d52 100644 --- a/frontend/src/components/MainNav.js +++ b/frontend/src/components/MainNav.js @@ -1,151 +1,82 @@ // eslint-disable-next-line -import React, { useContext } from 'react'; -import { NavLink, useLocation } from 'react-router-dom'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import ThemeContext from '../contexts/themeContext'; -import packageJson from '../../package.json'; +import React from 'react'; +import { NavLink } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { useLocation } from 'react-router-dom'; -const MainNav = () => { - const theme = useContext(ThemeContext); - const [t] = useTranslation(['mainnav']); +const NavBar = styled.nav` + background: var(--titan-green); + color: var(--nero-white); + display: flex; + align-items: center; + min-height: 100px; +`; - const pathname = useLocation().pathname; +const StyledH2 = styled.h2` + padding: 0px 40px 0px 40px; + font-family: 'Space Mono' !important; + letter-spacing: 1px; + @media only screen and (max-width: 540px) { + width: 0; + overflow: hidden; + display: block; + } +`; - const seriesUrl = pathname.includes('series'); - const suiteUrl = pathname.includes('suite'); +const LinkContainer = styled.div` + a { + margin: 0 15px 0 15px; + color: var(--nero-white) !important; + font-size: 16px; + line-height: 24px; + font-weight: bold; + text-decoration: none; + border-bottom: none; + } - const correctUrl = prop => { - if (pathname.includes(prop)) { - return pathname; - } - const beginningUrl = pathname.substring(0, pathname.lastIndexOf('/')); - return beginningUrl.concat('/' + prop); - }; + .about { + border-bottom: ${props => !props.team && '3px solid var(--nero-white)'}; + } - const mainNavStyles = css` - flex: 0 0 240px; - @media only screen and (max-width: 999px) { - flex: 0 0 120px; - } - h2 { - padding: 0px 20px 0px 20px; - @media only screen and (max-width: 768px) { - display: block; - width: 0; - height: 40px; - padding: 0; - margin: 0; - overflow: hidden; - &:after { - display: block; - width: 100px; - top: 15px; - left: 10px; - content: 'Epi'; - position: absolute; - } - } - } - p { - padding: 20px 20px 0px 20px; - @media only screen and (max-width: 768px) { - display: block; - width: 0; - height: 40px; - padding: 0; - margin: 0; - overflow: hidden; - &:after { - display: none; - } - } - } - ul { - list-style-type: none; - padding: 0; - li { - font-size: 16px; - padding: & + li { - padding-top: ${theme.spacing.xs / 2}px; - } + .team { + border-bottom: ${props => props.team && '3px solid var(--nero-white)'}; + } - a { - display: block; - width: 100%; - padding: 15px 20px; - @media only screen and (max-width: 999px) { - padding: 15px 20px 15px 10px; - } - transition: 0.33s background-color; - &:hover, - &:active { - background-color: #ccc; - transition: 0.1s background-color; - } - } - &.nav-github { - margin-top: 1em; - font-weight: bold; - } - } - a.active { - font-weight: bold; - background-color: #ccc; - } + @media only screen and (max-width: 540px) { + margin-left: 10px; + a { + margin: 0 5px 0 5px; } - .sub-url { - padding-left: 5%; - } - `; + } +`; + +const MainNav = () => { + const [t] = useTranslation(['mainnav']); + + const location = useLocation(); + const team = + location.pathname.includes('team') || + location.pathname.includes('series'); + return ( - + + {t('logo')} + + + {t('help')} + + + {t('team')} + + {t('github')} + + ); }; diff --git a/frontend/src/components/SelectedTeam.js b/frontend/src/components/SelectedTeam.js index 185c085e..d36c4cdb 100644 --- a/frontend/src/components/SelectedTeam.js +++ b/frontend/src/components/SelectedTeam.js @@ -6,119 +6,127 @@ import theme from '../styles/theme'; import BreadcrumbNav from './BreadcrumbNav'; import { pickIcon } from './TestIcon'; import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; +const CardStyles = styled.div` + background-color: var(--nero-white); + box-shadow: rgba(0, 0, 0, 0.16) 0px 5px 7px, rgba(0, 0, 0, 0.23) 0px 5px 7px; + margin: 10px; + padding: 10px; + line-height: 16px; +`; -const SelectedTeam = ({ selectedTeam }) => { - const [t] = useTranslation(['team']); - - const cardStyles = css` - background-color: var(--powder-white); - box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 4px, - rgba(0, 0, 0, 0.23) 0px 3px 4px; - margin: 10px; - padding: 10px; - line-height: 16px; +const HoverDiv = styled.div` + :hover { + cursor: pointer; + } +`; - .series:hover, - .builds:hover { - cursor: pointer; - } +const H3 = styled.h3` + font-size: 16px; + line-height: 20px; + margin-top: 0; +`; +const H4 = styled.h4` + font-size: 16px; + line-height: 20px; +`; - h3, - h4 { - font-size: 16px; - line-height: 20px; - } - h3 { - margin-top: 0; - } - .cardInfoContainer:hover { - background-color: var(--mithril-grey); - } +const CardInfo = styled.div` + @media only screen and (min-width: 1024px) { + min-width: 440px; + min-height: 220px; + } +`; - .cardValue { - color: var(--pirlo-blue); - } +const InfoContainer = styled.div` + :hover { + background-color: var(--hermanni-grey); + } +`; - @media only screen and (min-width: 1024px) { - .card { - min-width: 440px; - min-height: 220px; - } - } - `; +const CardValue = styled.span` + color: var(--pirlo-blue); +`; +const TeamCard = ({ data }) => { + const [t] = useTranslation(['team']); let history = useHistory(); + const { + id, + name, + builds, + last_build, + last_build_id, + last_started, + last_status, + } = data; + + const LastStarted = last_started.slice(0, 16); + const testStatusIcon = pickIcon(last_status); + + return ( + +
+

{name}

+
+ + history.push(`/series/${id}/overview`)} + role={'presentation'} + > +

{t('card.series.title')}

+ + {t('card.series.builds')}:{' '} + {builds} + +
+ + history.push( + `/series/${id}/build/${last_build}/overview` + ) + } + role={'presentation'} + > +

{t('card.last_build.title')}

+ + {t('card.last_build.build_number')}:{' '} + + {last_build} + + + + {t('card.last_build.build_id')}:{' '} + + {last_build_id} + + + + {t('card.last_build.last_build_started')}:{' '} + + {LastStarted} + + + + {t('card.last_build.last_status')}:{' '} + + {testStatusIcon} + + +
+
+
+ ); +}; +const SelectedTeam = ({ selectedTeam }) => { const flexContainer = { display: 'flex', flexWrap: 'wrap', - paddingTop: '20px' - }; - - const TeamCard = ({ data }) => { - const { - id, - name, - builds, - last_build, - last_build_id, - last_started, - last_status - } = data; - - const LastStarted = last_started.slice(0, 16); - const testStatusIcon = pickIcon(last_status); - - return ( -
-
-

{name}

-
-
-
history.push(`/series/${id}/history`)} - role={'presentation'} - > -

{t('card.series.title')}

-
- {t('card.series.builds')}:{' '} - {builds} -
-
-
- history.push( - `/series/${id}/build/${last_build}/history` - ) - } - role={'presentation'} - > -

{t('card.last_build.title')}

-
- {t('card.last_build.build_number')}:{' '} - {last_build} -
-
- {t('card.last_build.build_id')}:{' '} - {last_build_id} -
-
- {t('card.last_build.last_build_started')}:{' '} - {LastStarted} -
-
- {t('card.last_build.last_status')}:{' '} - {testStatusIcon} -
-
-
-
- ); + paddingTop: '20px', }; return ( @@ -143,8 +151,8 @@ SelectedTeam.propTypes = { all_builds: PropTypes.object, name: PropTypes.string, series: PropTypes.array, - series_count: PropTypes.number - }) + series_count: PropTypes.number, + }), }; export default SelectedTeam; diff --git a/frontend/src/components/TestIcon.js b/frontend/src/components/TestIcon.js index 88fc876d..4536f0a4 100644 --- a/frontend/src/components/TestIcon.js +++ b/frontend/src/components/TestIcon.js @@ -1,50 +1,65 @@ import React from 'react'; import FA from 'react-fontawesome'; import theme from '../styles/theme'; +import styled from 'styled-components'; +import { ReactComponent as Pass } from '../images/success.svg'; +import { ReactComponent as Fail } from '../images/fail.svg'; +import { ReactComponent as Skipped } from '../images/skip.svg'; +import { ReactComponent as NotFound } from '../images/not-found.svg'; -export const pickIcon = test_status => { +const Fanner = styled(FA)` + ${props => props.type === 'clock-o' && 'margin-right: 8px'} +`; + +const TestStatusIcon = ({ type, text, iconColor }) => { + return ( + <> + + {text} + + ); +}; + +export const pickIcon = (test_status, key) => { let result = ''; // move to utils, copied in many places switch (test_status) { case 'PASS': result = ( - + <> + + Pass + ); break; case 'FAIL': result = ( - + <> + + Fail + ); break; case 'SKIPPED': result = ( - + <> + + Skipped + ); break; case 'EMPTY': result = ( - + <> + + Empty + ); break; case 'TIME': result = ( { return result; }; -const TestStatusIcon = ({ type, text, iconColor }) => { - return ( - <> - - {text} - - ); -}; - export default TestStatusIcon; diff --git a/frontend/src/components/lastRunTable/Body.js b/frontend/src/components/buildTable/Body.jsx similarity index 97% rename from frontend/src/components/lastRunTable/Body.js rename to frontend/src/components/buildTable/Body.jsx index d0b5fe75..6a66e74e 100644 --- a/frontend/src/components/lastRunTable/Body.js +++ b/frontend/src/components/buildTable/Body.jsx @@ -4,13 +4,13 @@ import Row from './Row'; import { useStateValue } from '../../contexts/state'; import { useParams } from 'react-router'; -const Body = ({ id }) => { +const Body = () => { const [ { historyDataState: { history }, lastRunFilterPass, - lastRunFilterFail - } + lastRunFilterFail, + }, ] = useStateValue(); let { buildId } = useParams(); const buildNum = buildId; @@ -45,7 +45,7 @@ const Body = ({ id }) => { test_cases => test_cases.builds[0].test_status !== lastRunFilterPass.filterType - ) + ), }); }); } else { @@ -78,7 +78,7 @@ const Body = ({ id }) => { test_cases => test_cases.builds[0].test_status !== lastRunFilterFail.filterType - ) + ), }); }); } else { diff --git a/frontend/src/components/buildTable/Error.jsx b/frontend/src/components/buildTable/Error.jsx new file mode 100644 index 00000000..7a5b9f81 --- /dev/null +++ b/frontend/src/components/buildTable/Error.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { ErrorMsg } from './Error.styles'; + +const Error = ({ build }) => { + const errorMessage = build.messages[0] || ''; + const CHARACTER_LIMIT = 200; + + if (errorMessage !== '' && errorMessage.message.length > CHARACTER_LIMIT) { + errorMessage.message = + errorMessage.message.substring(0, CHARACTER_LIMIT) + '...'; + } + + return ( + + {errorMessage.message} + + ); +}; + +export default Error; diff --git a/frontend/src/components/buildTable/Error.styles.js b/frontend/src/components/buildTable/Error.styles.js new file mode 100644 index 00000000..3acd50cc --- /dev/null +++ b/frontend/src/components/buildTable/Error.styles.js @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const ErrorMsg = styled.td` + background: ${props => + props.build.status === 'FAIL' && 'var(--arabia-red) !important'}; +`; diff --git a/frontend/src/components/lastRunTable/Flakiness.js b/frontend/src/components/buildTable/Flakiness.js similarity index 84% rename from frontend/src/components/lastRunTable/Flakiness.js rename to frontend/src/components/buildTable/Flakiness.js index b1297058..7d32721a 100644 --- a/frontend/src/components/lastRunTable/Flakiness.js +++ b/frontend/src/components/buildTable/Flakiness.js @@ -1,5 +1,17 @@ import React from 'react'; import { pickIcon } from '../TestIcon'; +import styled from 'styled-components'; + +const StyledRow = styled.td` + font-size: 14px; + text-align: center !important; + span { + width: 13px; + } + span + span { + margin-left: 2px; + } +`; const Flakiness = ({ builds, id }) => { const indexOfBuild = builds.findIndex(b => { @@ -43,7 +55,7 @@ const Flakiness = ({ builds, id }) => { const flakinessIcons = flakinessData.map(({ test_status }, i) => { return {pickIcon(test_status)}; }); - return {flakinessIcons}; + return {flakinessIcons}; }; export default Flakiness; diff --git a/frontend/src/components/lastRunTable/Row.js b/frontend/src/components/buildTable/Row.jsx similarity index 75% rename from frontend/src/components/lastRunTable/Row.js rename to frontend/src/components/buildTable/Row.jsx index a06c028f..409413ab 100644 --- a/frontend/src/components/lastRunTable/Row.js +++ b/frontend/src/components/buildTable/Row.jsx @@ -1,7 +1,5 @@ // eslint-disable-next-line import React, { Fragment } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; import Flakiness from './Flakiness'; import Status from './Status'; import Error from './Error'; @@ -9,25 +7,10 @@ import TestCase from './TestCase'; import { dashify } from '../../utils/helpers'; import { useLocation } from 'react-router'; import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { SuiteRow, LinkSuiteName } from './Row.styles'; const Row = ({ test_cases, suite, id, suiteId }) => { - const tableRowStyles = css` - .test-time-row { - text-align: right; - } - - .flakiness-row { - font-size: 14px; - text-align: center; - span { - width: 13px; - } - span + span { - margin-left: 2px; - } - } - `; - const tableRow = test_cases.map(({ builds, test_id }, index) => { let filteredBuilds = null; if (id) { @@ -40,10 +23,10 @@ const Row = ({ test_cases, suite, id, suiteId }) => { ? filteredBuilds[0] : builds[0]; - const testRunTime = build ? build.test_run_time : 'ei ole'; + const testRunTime = build ? build.test_run_time : 'not found'; return ( - + {index === 0 && ( { - {(testRunTime / 1000).toFixed(3)}s + {(testRunTime / 1000).toFixed(2)}s - + ); }); @@ -75,6 +58,8 @@ export default Row; // Show suite name separated on different lines with dots showing depth level const LinksSuiteName = ({ tableCellHeight, suiteName, suiteId }) => { + const [t] = useTranslation(['history']); + const pathname = useLocation().pathname; const correctUrl = pathname.substring(0, pathname.lastIndexOf('/')); let tempSuiteName = suiteName.split('.'); @@ -83,18 +68,21 @@ const LinksSuiteName = ({ tableCellHeight, suiteName, suiteId }) => { let el = tempSuiteName[index]; splitSuiteName.push( - .{el} + .{el}
); } return ( - + - Build +
{t('build.table.row.build')}
{splitSuiteName} - +
); }; diff --git a/frontend/src/components/buildTable/Row.styles.js b/frontend/src/components/buildTable/Row.styles.js new file mode 100644 index 00000000..34734499 --- /dev/null +++ b/frontend/src/components/buildTable/Row.styles.js @@ -0,0 +1,37 @@ +import styled from 'styled-components'; + +export const SuiteRow = styled.tr` + border-top: ${props => props.position !== 0 && 'none !important'}; +`; + +export const LinkSuiteName = styled.td` + text-align: left !important; + + span:nth-child(4) { + padding-left: 8px; + } + span:nth-child(6) { + padding-left: 16px; + } + span:nth-child(8) { + padding-left: 24px; + } + span:nth-child(10) { + padding-left: 32px; + } + span:nth-child(12) { + padding-left: 40px; + } + span:nth-child(14) { + padding-left: 48px; + } + span:nth-child(16) { + padding-left: 56px; + } + span:nth-child(18) { + padding-left: 64px; + } + span:nth-child(20) { + padding-left: 72px; + } +`; diff --git a/frontend/src/components/lastRunTable/Status.js b/frontend/src/components/buildTable/Status.jsx similarity index 83% rename from frontend/src/components/lastRunTable/Status.js rename to frontend/src/components/buildTable/Status.jsx index eb39bdfa..61ae62b8 100644 --- a/frontend/src/components/lastRunTable/Status.js +++ b/frontend/src/components/buildTable/Status.jsx @@ -1,6 +1,7 @@ // eslint-disable-next-line import React from 'react'; import { pickIcon } from '../TestIcon'; + const Status = ({ build, selectedBuild }) => { const testStatus = Number(selectedBuild) === build.build_number @@ -8,7 +9,7 @@ const Status = ({ build, selectedBuild }) => { : 'EMPTY'; const testStatusIcon = pickIcon(testStatus); - return {testStatusIcon}; + return {testStatusIcon}; }; export default Status; diff --git a/frontend/src/components/buildTable/Table.jsx b/frontend/src/components/buildTable/Table.jsx new file mode 100644 index 00000000..19768ff5 --- /dev/null +++ b/frontend/src/components/buildTable/Table.jsx @@ -0,0 +1,37 @@ +// eslint-disable-next-line +import React from 'react'; +import Body from './Body'; +import { useTranslation } from 'react-i18next'; +import { StyledTable, HeaderRow, Container } from './Table.styles'; + +const Table = ({ id }) => { + const [t] = useTranslation(['history']); + + return ( + + + + + {t('build.table.suite')} + + {t('build.table.status')} + + {t('build.table.test')} + {t('build.table.suite')} + + {t('build.table.time')} + + + {t('build.table.flakiness')} + + + + + + + + + ); +}; + +export default Table; diff --git a/frontend/src/components/buildTable/Table.styles.js b/frontend/src/components/buildTable/Table.styles.js new file mode 100644 index 00000000..2790f436 --- /dev/null +++ b/frontend/src/components/buildTable/Table.styles.js @@ -0,0 +1,44 @@ +import { baseTable } from '../../styles/baseComponents'; +import styled from 'styled-components'; + +export const StyledTable = styled(baseTable)` + table-layout: fixed; + border-collapse: collapse; + overflow: auto; + + thead th:nth-of-type(1) { + width: 10%; + text-align: left !important; + } + + thead th:nth-of-type(2) { + width: 2.8%; + } + + thead th:nth-of-type(3) { + text-align: left !important; + width: 10%; + } + + thead th:nth-of-type(4) { + width: 8%; + } + + thead th:nth-of-type(5) { + width: 3%; + } + + thead th:nth-of-type(6) { + width: 4%; + } +`; + +export const Container = styled.div` + overflow: hidden; + border: 1px solid #e5e5e5; + border-radius: 4px 4px 0px 0px; +`; + +export const HeaderRow = styled.tr` + border-top: none !important; +`; diff --git a/frontend/src/components/lastRunTable/TestCase.js b/frontend/src/components/buildTable/TestCase.jsx similarity index 83% rename from frontend/src/components/lastRunTable/TestCase.js rename to frontend/src/components/buildTable/TestCase.jsx index 8875825e..20574cec 100644 --- a/frontend/src/components/lastRunTable/TestCase.js +++ b/frontend/src/components/buildTable/TestCase.jsx @@ -1,16 +1,17 @@ import React from 'react'; import { Link, useLocation } from 'react-router-dom'; +import { StyledTests } from './TextCase.styles'; const TestCase = ({ testCases, index, suiteId, testId }) => { const testCase = testCases[index].test_case; const pathname = useLocation().pathname; const correctUrl = pathname.substring(0, pathname.lastIndexOf('/')); return ( - + {testCase} - + ); }; diff --git a/frontend/src/components/buildTable/TextCase.styles.js b/frontend/src/components/buildTable/TextCase.styles.js new file mode 100644 index 00000000..b4ac0db3 --- /dev/null +++ b/frontend/src/components/buildTable/TextCase.styles.js @@ -0,0 +1,5 @@ +import styled from 'styled-components'; + +export const StyledTests = styled.td` + text-align: left !important; +`; diff --git a/frontend/src/components/buttons/BuildAmountSelector.jsx b/frontend/src/components/buttons/BuildAmountSelector.jsx new file mode 100644 index 00000000..fb71bddf --- /dev/null +++ b/frontend/src/components/buttons/BuildAmountSelector.jsx @@ -0,0 +1,55 @@ +// eslint-disable-next-line +import React from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { useQueryParams } from '../../hooks/useQuery'; + +import { useStateValue } from '../../contexts/state'; +import DropdownMenu from './DropdownMenu'; + +import { Header, SelectorContainer } from './BuildAmountSelector.styles'; + +const BuildAmountSelector = () => { + const [, dispatch] = useStateValue(); + + const history = useHistory(); + const location = useLocation(); + const queryParams = useQueryParams(); + + const updateTags = tag => { + queryParams.set('numberofbuilds', tag); + return queryParams.toString(); + }; + + const handleChange = e => { + dispatch({ + type: 'setAmountOfBuilds', + amountOfBuilds: e.value, + }); + history.push({ + pathname: `${location.pathname}`, + search: `?${updateTags(e.value)}`, + state: {}, + }); + }; + + const selectorValues = [ + { value: 5, label: 5, id: '5_option' }, + { value: 10, label: 10, id: '10_option' }, + { value: 15, label: 15, id: '15_option' }, + { value: 30, label: 30, id: '30_option' }, + ]; + + return ( + +
Builds
+ +
+ ); +}; + +export default BuildAmountSelector; diff --git a/frontend/src/components/buttons/BuildAmountSelector.styles.js b/frontend/src/components/buttons/BuildAmountSelector.styles.js new file mode 100644 index 00000000..c801f5fe --- /dev/null +++ b/frontend/src/components/buttons/BuildAmountSelector.styles.js @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +export const SelectorContainer = styled.div` + margin: 16px 0; + width: 130px; +`; + +export const Header = styled.div` + color: var(--evidence-grey); + margin: 8px 0; +`; diff --git a/frontend/src/components/buttons/DropdownMenu.jsx b/frontend/src/components/buttons/DropdownMenu.jsx new file mode 100644 index 00000000..5b2dd96a --- /dev/null +++ b/frontend/src/components/buttons/DropdownMenu.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import Select, { components } from 'react-select'; +import { ReactComponent as Down } from '../../images/caret-down.svg'; + +const DropdownIndicator = props => { + return ( + + + + ); +}; + +const DropdownMenu = ({ selectorValues, onChange, defaultValue, id }) => { + return ( + <> + handleFilterChange(e)} - className={ - title === parseInt(amountOfBuilds, 10) ? 'selected' : 'disabled' - } - /> - ); + if (title === parseInt(amountOfBuilds, 10)) { + return ( + handleFilterChange(e)} + className={title === 'selected'} + /> + ); + } else { + return ( + handleFilterChange(e)} + className={title === 'disabled'} + /> + ); + } }; -const ButtonGroup = ({ options }) => { +const ButtonGroup = ({ options, direction }) => { return ( -
+ {options.map((i, index) => { return ; })} -
+ ); }; export default Filter; diff --git a/frontend/src/components/historyTable/Heading.js b/frontend/src/components/historyTable/Heading.jsx similarity index 53% rename from frontend/src/components/historyTable/Heading.js rename to frontend/src/components/historyTable/Heading.jsx index 532f9f75..44286eec 100644 --- a/frontend/src/components/historyTable/Heading.js +++ b/frontend/src/components/historyTable/Heading.jsx @@ -1,8 +1,6 @@ +import React from 'react'; import { useStateValue } from '../../contexts/state'; -import { Link } from 'react-router-dom'; -/** @jsx jsx */ - -import { css, jsx } from '@emotion/core'; +import { StyledLink } from './Heading.styles'; // helper for build number sorting function compareNumbers(a, b) { @@ -10,32 +8,13 @@ function compareNumbers(a, b) { } const Heading = () => { - const headingStyles = css` - background: blue; - .run-link-wrapper { - padding: 0; - } - .run-link { - display: block; - width: 100%; - margin: 0; - padding: 10px; - height: 100%; - transition: 0.33s background-color; - &:hover, - &:active { - background-color: #ccc; - transition: 0.1s background-color; - } - } - `; const [ { historyDataState: { max_build_num }, amountOfBuilds, - selectedBranchState + selectedBranchState, }, - dispatch + dispatch, ] = useStateValue(); let { id } = selectedBranchState; let headingBuildNumbers = []; @@ -50,7 +29,7 @@ const Heading = () => { const selectedBuild = e.target.innerText.slice(6); dispatch({ type: 'setSelectedBuild', - selectedBuild + selectedBuild, }); }; @@ -59,22 +38,15 @@ const Heading = () => { .sort(compareNumbers) .reverse() .map(buildNumber => ( - handleBuildClick(e)} - > - + handleBuildClick(e)}> + Build {buildNumber} - + )); return ( - + Suite Test diff --git a/frontend/src/components/historyTable/Heading.styles.js b/frontend/src/components/historyTable/Heading.styles.js new file mode 100644 index 00000000..5e66f241 --- /dev/null +++ b/frontend/src/components/historyTable/Heading.styles.js @@ -0,0 +1,12 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +export const StyledLink = styled(Link)` + display: block; + width: 100%; + margin: 0; + padding: 0px; + text-align: center; + height: 100%; + transition: 0.33s background-color; +`; diff --git a/frontend/src/components/historyTable/Suite.js b/frontend/src/components/historyTable/Suite.jsx similarity index 75% rename from frontend/src/components/historyTable/Suite.js rename to frontend/src/components/historyTable/Suite.jsx index 8ed84fc8..5b7083ea 100644 --- a/frontend/src/components/historyTable/Suite.js +++ b/frontend/src/components/historyTable/Suite.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { dashify } from '../../utils/helpers'; -import SuiteName from '../SuiteName'; +import SuiteName from './SuiteName'; import TestStatus from './TestStatus'; import TestCase from './TestCase'; @@ -13,8 +13,8 @@ const Suite = ({ builds, test_case, suite, index, test_cases }) => { tableCellHeight={test_cases.length} /> )} - - + + ); }; diff --git a/frontend/src/components/SuiteName.js b/frontend/src/components/historyTable/SuiteName.jsx similarity index 67% rename from frontend/src/components/SuiteName.js rename to frontend/src/components/historyTable/SuiteName.jsx index 720561d6..77bff5a8 100644 --- a/frontend/src/components/SuiteName.js +++ b/frontend/src/components/historyTable/SuiteName.jsx @@ -1,5 +1,6 @@ import React, { Fragment } from 'react'; -import { dashify } from '../utils/helpers'; +import { dashify } from '../../utils/helpers'; +import { StyledData } from './SuiteName.styles'; // Show suite name separated on different lines with dots showing depth level const SuiteName = ({ tableCellHeight, suiteName }) => { @@ -9,15 +10,18 @@ const SuiteName = ({ tableCellHeight, suiteName }) => { let el = tempSuiteName[index]; splitSuiteName.push( - .{el} + .{el}
); } return ( - + {splitSuiteName} - + ); }; diff --git a/frontend/src/components/historyTable/SuiteName.styles.js b/frontend/src/components/historyTable/SuiteName.styles.js new file mode 100644 index 00000000..01ec8d82 --- /dev/null +++ b/frontend/src/components/historyTable/SuiteName.styles.js @@ -0,0 +1,39 @@ +import styled from 'styled-components'; + +export const StyledData = styled.td` + padding: 10px; + text-align: left; + vertical-align: top; + background: var(--nero-white); + min-width: 25%; + + @media only screen and (min-width: 1280px) { + span:nth-child(3) { + padding-left: 8px; + } + span:nth-child(5) { + padding-left: 16px; + } + span:nth-child(7) { + padding-left: 24px; + } + span:nth-child(9) { + padding-left: 32px; + } + span:nth-child(11) { + padding-left: 40px; + } + span:nth-child(13) { + padding-left: 48px; + } + span:nth-child(15) { + padding-left: 56px; + } + span:nth-child(17) { + padding-left: 64px; + } + span:nth-child(19) { + padding-left: 72px; + } + } +`; diff --git a/frontend/src/components/historyTable/Table.js b/frontend/src/components/historyTable/Table.js deleted file mode 100644 index 31cc72ec..00000000 --- a/frontend/src/components/historyTable/Table.js +++ /dev/null @@ -1,63 +0,0 @@ -// eslint-disable-next-line -import React, { useContext } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import Heading from './Heading'; -import Body from './Body'; -import NotFound from '../NotFound'; -import { useStateValue } from '../../contexts/state'; - -const Table = () => { - const tableStyles = css` - overflow: auto; - clear: both; - - table { - border-collapse: collapse; - table-layout: fixed; - } - table, - th, - td { - padding: 10px; - border: 1px solid black; - text-align: left; - vertical-align: top; - } - th { - background: #ddd; - } - td { - background: var(--powder-white); - } - td.test-result-undefined { - background: var(--mithril-grey); - } - .centerTableCellContent { - text-align: center; - vertical-align: middle; - } - `; - const [ - { - historyDataState: { max_build_num } - } - ] = useStateValue(); - - if (max_build_num > 0) { - return ( -
- - - - - -
-
- ); - } else { - return ; - } -}; - -export default Table; diff --git a/frontend/src/components/historyTable/Table.jsx b/frontend/src/components/historyTable/Table.jsx new file mode 100644 index 00000000..fe09b06c --- /dev/null +++ b/frontend/src/components/historyTable/Table.jsx @@ -0,0 +1,32 @@ +// eslint-disable-next-line +import React from 'react'; +import Heading from './Heading'; +import Body from './Body'; +import NotFound from '../NotFound'; +import { useStateValue } from '../../contexts/state'; +import { TableStyled } from './Table.styles'; + +const Table = () => { + const [ + { + historyDataState: { max_build_num }, + }, + ] = useStateValue(); + + if (max_build_num > 0) { + return ( +
+ + + + + + +
+ ); + } else { + return ; + } +}; + +export default Table; diff --git a/frontend/src/components/historyTable/Table.styles.js b/frontend/src/components/historyTable/Table.styles.js new file mode 100644 index 00000000..b08e3687 --- /dev/null +++ b/frontend/src/components/historyTable/Table.styles.js @@ -0,0 +1,32 @@ +import styled from 'styled-components'; + +export const TableStyled = styled.table` + overflow: auto; + clear: both; + border-collapse: separate !important; + border-spacing: 0; + table-layout: fixed; + border: 1px solid #e5e5e5; + border-radius: 4px 4px 0px 0px; + text-align: left; + vertical-align: top; + + thead { + background: var(--hermanni-grey); + } + + thead th { + padding: 10px; + text-align: left; + vertical-align: middle; + } + + tbody td { + border-top: 1px solid #e5e5e5; + } + + .centerTableCellContent { + text-align: center; + vertical-align: middle; + } +`; diff --git a/frontend/src/components/historyTable/TestCase.js b/frontend/src/components/historyTable/TestCase.js deleted file mode 100644 index 4be87170..00000000 --- a/frontend/src/components/historyTable/TestCase.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { dashify } from '../../utils/helpers'; - -const TestCase = ({ test_case }) => ( - {test_case} -); - -export default TestCase; diff --git a/frontend/src/components/historyTable/TestCase.jsx b/frontend/src/components/historyTable/TestCase.jsx new file mode 100644 index 00000000..21a65a8a --- /dev/null +++ b/frontend/src/components/historyTable/TestCase.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { dashify } from '../../utils/helpers'; +import { StyledData } from './TestCase.styles'; + +const TestCase = ({ test_case, position }) => ( + + {test_case} + +); + +export default TestCase; diff --git a/frontend/src/components/historyTable/TestCase.styles.js b/frontend/src/components/historyTable/TestCase.styles.js new file mode 100644 index 00000000..42a7c867 --- /dev/null +++ b/frontend/src/components/historyTable/TestCase.styles.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; + +export const StyledData = styled.td` + padding: 10px; + text-align: left; + vertical-align: middle; + background: var(--nero-white); + border-top: ${props => props.position !== 0 && 'none !important'}; +`; diff --git a/frontend/src/components/historyTable/TestStatus.js b/frontend/src/components/historyTable/TestStatus.jsx similarity index 73% rename from frontend/src/components/historyTable/TestStatus.js rename to frontend/src/components/historyTable/TestStatus.jsx index f7961344..40a02c81 100644 --- a/frontend/src/components/historyTable/TestStatus.js +++ b/frontend/src/components/historyTable/TestStatus.jsx @@ -1,13 +1,14 @@ import React from 'react'; import { useStateValue } from '../../contexts/state'; import { pickIcon } from '../TestIcon'; +import { DefinedData } from './TestStatus.styles'; -const TableTestStatusCell = ({ builds }) => { +const TableTestStatusCell = ({ builds, position }) => { const [ { historyDataState: { max_build_num }, - amountOfBuilds - } + amountOfBuilds, + }, ] = useStateValue(); // Creates correct length (amountOfBuilds) of array populated with empty values. @@ -21,7 +22,7 @@ const TableTestStatusCell = ({ builds }) => { arr.push({ build_number: max_build_num - i, test_status: '', - suite_start_time: '' + suite_start_time: '', }); } return arr.map((filledBuild, i) => { @@ -33,16 +34,22 @@ const TableTestStatusCell = ({ builds }) => { } catch (error) { //console.error(error); } - const testStatusIcon = pickIcon(test_status); + let testStatusIcon; + + !test_status + ? (testStatusIcon = pickIcon('EMPTY')) + : (testStatusIcon = pickIcon(test_status)); return ( - {testStatusIcon} - + ); }); }; diff --git a/frontend/src/components/historyTable/TestStatus.styles.js b/frontend/src/components/historyTable/TestStatus.styles.js new file mode 100644 index 00000000..b715b523 --- /dev/null +++ b/frontend/src/components/historyTable/TestStatus.styles.js @@ -0,0 +1,10 @@ +import styled from 'styled-components'; + +export const DefinedData = styled.td` + padding: 10px; + text-align: center; + vertical-align: middle; + background: ${props => + !props.status ? 'var(--hermanni-grey)' : 'var(--nero-white)'}; + border-top: ${props => props.position !== 0 && 'none !important'}; +`; diff --git a/frontend/src/components/lastBuildElement/BuildInfoTable.js b/frontend/src/components/lastBuildElement/BuildInfoTable.js new file mode 100644 index 00000000..64b2fb9c --- /dev/null +++ b/frontend/src/components/lastBuildElement/BuildInfoTable.js @@ -0,0 +1,55 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useStateValue } from '../../contexts/state'; + +const LastBuildTable = styled.table` + width: 100%; + th { + margin: 20px; + text-align: left; + vertical-align: middle; + } + + td { + margin: 20px; + padding-right: 5px; + text-align: left; + vertical-align: middle; + } + + td:nth-of-type(1) { + width: 40%; + } + + td:nth-of-type(2) { + width: 50%; + } +`; + +const BuildInfoTable = () => { + const [ + { + parentData: { seriesData }, + }, + ] = useStateValue(); + return ( + + + + Last Build ID + {seriesData.last_build_id} + + + Start Time + {seriesData.last_started} + + + Status + {seriesData.last_status} + + + + ); +}; + +export default BuildInfoTable; diff --git a/frontend/src/components/lastBuildElement/FailuresTable.js b/frontend/src/components/lastBuildElement/FailuresTable.js new file mode 100644 index 00000000..d187f8b9 --- /dev/null +++ b/frontend/src/components/lastBuildElement/FailuresTable.js @@ -0,0 +1,51 @@ +import React from 'react'; +import styled from 'styled-components'; + +const LastBuildTable = styled.table` + width: 100%; + th { + margin: 20px; + text-align: left; + vertical-align: middle; + border-bottom: 1px solid #ddd; + } + + td { + padding-right: 5px; + margin: 20px; + text-align: left; + vertical-align: middle; + } + thead th:nth-of-type(1) { + width: 40%; + } + + thead th:nth-of-type(2) { + width: 50%; + } +`; + +const FailuresTable = ({ failures }) => { + return ( + + + + Suite + Test Case + + + + {failures.map(x => { + return ( + + {x.suite} + {x.name} + + ); + })} + + + ); +}; + +export default FailuresTable; diff --git a/frontend/src/components/lastBuildElement/LastBuild.js b/frontend/src/components/lastBuildElement/LastBuild.js new file mode 100644 index 00000000..f4d744f3 --- /dev/null +++ b/frontend/src/components/lastBuildElement/LastBuild.js @@ -0,0 +1,70 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import FailuresTable from './FailuresTable'; +import BuildInfoTable from './BuildInfoTable'; +import { useParams } from 'react-router'; +import { useStateValue } from '../../contexts/state'; + +const Divi = styled.div` + border-bottom: 1px solid #ddd; + min-width: 300px; + font-weight: bold; +`; + +const Containing = styled.div` + min-width: 300px; +`; + +const LastBuildElement = () => { + const { seriesId } = useParams(); + const [failures, setFailures] = useState([]); + const [{ offset }, dispatch] = useStateValue(); + + useEffect(() => { + let mounted = true; + const fetchHistory = async () => { + const url = `/data/series/${seriesId}/history?builds=1&offset=${offset}`; + try { + const res = await fetch(url); + const json = await res.json(); + //The amount of failures of a test in test_cases is counted and made into an object. The objects are then sorted and only the selected amount are shown. + const filterList = json.history + .filter(test => test.test_cases.length !== 0) + .flatMap(x => x.test_cases) + .map(x => { + const failures = x.builds.filter( + y => y.status === 'FAIL' + ); + const split_name = x.full_name.split('.'); + return { + name: x.test_case, + id: x.test_id, + suite: split_name[split_name.length - 2], + failures: failures, + }; + }) + .filter(data => data.failures.length !== 0); + if (mounted) { + setFailures(filterList); + } + } catch (error) { + dispatch({ type: 'setErrorState', errorState: error }); + } + }; + fetchHistory(); + + return () => (mounted = false); + }, [seriesId, offset]); + + return ( + + Last Build Status + + Failing Test Cases + + + ); +}; + +export default LastBuildElement; diff --git a/frontend/src/components/lastRunTable/Error.js b/frontend/src/components/lastRunTable/Error.js deleted file mode 100644 index 23e539d3..00000000 --- a/frontend/src/components/lastRunTable/Error.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; - -const Error = ({ build }) => { - const errorMessage = build.messages[0] || ''; - const CHARACTER_LIMIT = 200; - - if (errorMessage !== '' && errorMessage.message.length > CHARACTER_LIMIT) { - errorMessage.message = - errorMessage.message.substring(0, CHARACTER_LIMIT) + '...'; - } - - return {errorMessage.message}; -}; - -export default Error; diff --git a/frontend/src/components/lastRunTable/MetadataTable.js b/frontend/src/components/lastRunTable/MetadataTable.js deleted file mode 100644 index af6507b5..00000000 --- a/frontend/src/components/lastRunTable/MetadataTable.js +++ /dev/null @@ -1,59 +0,0 @@ -// eslint-disable-next-line -import React, { useContext } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import ThemeContext from '../../contexts/themeContext'; -import { useStateValue } from '../../contexts/state'; - -const MetadataTable = () => { - const [{ metadataState }] = useStateValue(); - const theme = useContext(ThemeContext); - - const tableStyles = css` - ${theme.baseTableStyle} - overflow: auto; - margin-bottom: 10px; - margin-top: 20px; - `; - - const metadata = metadataState.metadata - ? metadataState.metadata.filter( - ({ suite_id }) => suite_id === metadataState.metadata[0].suite_id - ) - : null; - - return ( -
- - - - - - - - - {metadata !== null && - metadata.map( - ({ metadata_name, metadata_value }, index) => { - const value = - metadata_value.length > 200 - ? `${metadata_value.substring( - 0, - 200 - )}...` - : metadata_value; - return ( - - - - - ); - } - )} - -
MetadataValue
{metadata_name}{value}
-
- ); -}; - -export default MetadataTable; diff --git a/frontend/src/components/lastRunTable/Table.js b/frontend/src/components/lastRunTable/Table.js deleted file mode 100644 index cf8e6c0e..00000000 --- a/frontend/src/components/lastRunTable/Table.js +++ /dev/null @@ -1,70 +0,0 @@ -// eslint-disable-next-line -import React, { useContext } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import ThemeContext from '../../contexts/themeContext'; -import Body from './Body'; - -const Table = ({ id }) => { - const theme = useContext(ThemeContext); - - const tableStyles = css` - ${theme.baseTableStyle} - overflow: auto; - - table { - table-layout: fixed; - width: 100%; - border-collapse: collapse; - } - - thead th:nth-of-type(1) { - width: 10%; - } - - thead th:nth-of-type(2) { - width: 2.8%; - } - - thead th:nth-of-type(3) { - width: 10%; - } - - thead th:nth-of-type(4) { - width: 8%; - } - - thead th:nth-of-type(5) { - width: 3%; - } - - thead th:nth-of-type(6) { - width: 4%; - } - overflow: auto; - - ${theme.baseTableStyle} - `; - - return ( -
- - - - - - - - - - - - - - -
SuitenameStatusTestsError messagesTimeFlakiness
-
- ); -}; - -export default Table; diff --git a/frontend/src/components/metadata/BuildMetadata.jsx b/frontend/src/components/metadata/BuildMetadata.jsx new file mode 100644 index 00000000..f3db54fb --- /dev/null +++ b/frontend/src/components/metadata/BuildMetadata.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import MetadataTable from './MetadataTable'; +import { capitalCaseInitial, removeUnderscore } from '../../utils/helpers'; +import { useStateValue } from '../../contexts/state'; + +const BuildMetadata = () => { + const [{ metadataState }] = useStateValue(); + + const metadata = + metadataState && metadataState.metadata + ? metadataState.metadata.filter( + ({ suite_id }) => + suite_id === metadataState.metadata[0].suite_id + ) + : null; + + const name = + metadata && + metadata.map(({ metadata_name }) => + removeUnderscore(capitalCaseInitial(metadata_name)) + ); + const value = + metadata && + metadata.map(({ metadata_value }) => + removeUnderscore(capitalCaseInitial(metadata_value)) + ); + + return metadata && ; +}; + +export default BuildMetadata; diff --git a/frontend/src/components/metadata/MetadataTable.jsx b/frontend/src/components/metadata/MetadataTable.jsx new file mode 100644 index 00000000..674e0b48 --- /dev/null +++ b/frontend/src/components/metadata/MetadataTable.jsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Container, + HeaderContainer, + SplitBorder, + TableContainer, + DataRow, +} from './MetadataTable.styles'; +import { ReactComponent as Up } from '../../images/chevron-up.svg'; +import { ReactComponent as Down } from '../../images/chevron-down.svg'; + +const MetadataTable = ({ name, value }) => { + const [t] = useTranslation(['metadata']); + const [Open, setOpen] = useState(true); + + return ( + + setOpen(!Open)} + onKeyPress={() => setOpen(!Open)} + role="button" + tabIndex="0" + > +

{t('metadata')}

+ {Open ? : } +
+ + + + {t('name')} + {name.map((n, index) => ( + {n} + ))} + + + {t('value')} + {value.map((v, index) => + v.includes('Http') || v.includes('http') ? ( + + {v} + + ) : ( + {v} + ) + )} + + +
+ ); +}; + +export default MetadataTable; diff --git a/frontend/src/components/metadata/MetadataTable.styles.js b/frontend/src/components/metadata/MetadataTable.styles.js new file mode 100644 index 00000000..b7e273cc --- /dev/null +++ b/frontend/src/components/metadata/MetadataTable.styles.js @@ -0,0 +1,68 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + border: 1px solid var(--hermanni-grey); + padding: 0 16px; + width: 100%; +`; + +export const HeaderContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + :hover { + cursor: pointer; + } + + font-size: 20px; + font-family: 'Noto Serif'; +`; + +export const SplitBorder = styled.div` + background: var(--hermanni-grey); + opacity: 0.4; + height: ${props => (props.open ? '4px' : '0px')}; +`; + +export const TableContainer = styled.div` + display: flex; + + .Open, + .Close { + max-height: 0; + overflow-y: hidden; + padding: 0; + -webkit-transition: max-height 0.3s ease-in-out; + -moz-transition: max-height 0.3s ease-in-out; + -o-transition: max-height 0.3s ease-in-out; + transition: max-height 0.3s ease-in-out; + -webkit-transition: padding 0.25s ease; + -moz-transition: padding 0.25s ease; + -o-transition: padding 0.25s ease; + transition: padding 0.25s ease; + } + + .Open { + max-height: 500px; + padding: 16px 0; + } +`; + +export const DataRow = styled.div` + display: flex; + flex-direction: column; + flex: ${props => (props.first ? '0.35' : '0.65')}; + + span { + border-bottom: 1px solid var(--hermanni-grey); + } + span:first-child, + span:last-child { + border-bottom: none; + } + span:first-child { + font-weight: bolder; + padding-bottom: 8px; + } +`; diff --git a/frontend/src/components/metadata/SuiteMetadata.jsx b/frontend/src/components/metadata/SuiteMetadata.jsx new file mode 100644 index 00000000..74cef50f --- /dev/null +++ b/frontend/src/components/metadata/SuiteMetadata.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import MetadataTable from './MetadataTable'; +import { useStateValue } from '../../contexts/state'; + +const SuiteMetadata = () => { + const [{ selectedSuiteState }] = useStateValue(); + + const name = [ + 'Suite Id:', + 'Name:', + 'Full name:', + 'Repository:', + 'Starttime:', + ]; + + const value = selectedSuiteState && [ + selectedSuiteState.suite.id.toString(), + selectedSuiteState.suite.name, + selectedSuiteState.suite.full_name, + selectedSuiteState.suite.repository, + selectedSuiteState.suite.start_time + ? selectedSuiteState.suite.start_time.slice(0, 16) + : '', + ]; + + return selectedSuiteState && ; +}; + +export default SuiteMetadata; diff --git a/frontend/src/components/overview/Build.jsx b/frontend/src/components/overview/Build.jsx new file mode 100644 index 00000000..82451ea1 --- /dev/null +++ b/frontend/src/components/overview/Build.jsx @@ -0,0 +1,72 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router'; +import PieChart from '../graphs/PieChart'; +import { suiteLabels, testLabels } from '../../utils/graphTypes'; +import Loading from '../Loading'; +import { useStateValue } from '../../contexts/state'; +import { useTranslation } from 'react-i18next'; +import { last } from 'ramda'; +import BuildMetadata from '../metadata/BuildMetadata'; +import useMetadata from '../../hooks/useMetadata'; +import { FlexDiv, ChartContainer, ElementHeader } from './Build.styles'; + +const Build = () => { + const [t] = useTranslation(['overview']); + + const { seriesId, buildId } = useParams(); + + const [dispatch] = useStateValue(); + const [statusCount, setStatusCount] = useState(); + + useMetadata(); + + useEffect(() => { + const url = `/data/series/${seriesId}/status_counts/?start_from=${buildId}&builds=1`; + + const fetchData = async () => { + try { + const res = await fetch(url); + const json = await res.json(); + setStatusCount(json.status_counts); + } catch (error) { + dispatch({ type: 'setErrorState', errorState: error }); + } + }; + fetchData(); + }, [buildId, seriesId]); + + const cleanseData = () => { + return statusCount && statusCount.length > 1 + ? [last(statusCount)] + : statusCount; + }; + + return ( + + + {statusCount ? ( + + + {t('build.suite')} + + + + {t('build.test')} + + + + ) : ( + + )} + + ); +}; + +export default Build; diff --git a/frontend/src/components/overview/Build.styles.js b/frontend/src/components/overview/Build.styles.js new file mode 100644 index 00000000..ea8cab5a --- /dev/null +++ b/frontend/src/components/overview/Build.styles.js @@ -0,0 +1,21 @@ +import styled from 'styled-components'; +import { overviewElement } from '../../styles/baseComponents'; + +export const FlexDiv = styled.div` + display: flex; + flex-wrap: wrap; + width: 100%; +`; + +export const ChartContainer = styled(overviewElement)` + margin: 20px 40px 40px 0; + background-color: var(--nero-white); + width: ${props => props.width}; + height: ${props => props.height}; +`; + +export const ElementHeader = styled.h3` + text-align: center; + margin: 10px; + font-family: 'Space Mono'; +`; diff --git a/frontend/src/components/overview/Series.jsx b/frontend/src/components/overview/Series.jsx new file mode 100644 index 00000000..20b4c727 --- /dev/null +++ b/frontend/src/components/overview/Series.jsx @@ -0,0 +1,68 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { useStateValue } from '../../contexts/state'; +import TimeLineChart from '../graphs/TimeLineChart'; +import DashboardList from '../dashlist/ListMain'; +import LastBuildElement from '../lastBuildElement/LastBuild'; +import { useTranslation } from 'react-i18next'; +import { ChartContainer, ElementHeader } from './Series.styles'; + +const Series = () => { + const [t] = useTranslation(['overview']); + + const { seriesId } = useParams(); + + const [{ branchesState }, dispatch] = useStateValue(); + + useEffect(() => { + if (branchesState) { + const branch = branchesState.series?.find( + ({ id: serie_id }) => serie_id === parseInt(seriesId, 10) + ); + const fetchData = async () => { + dispatch({ type: 'setSeriesData', seriesData: branch }); + dispatch({ + type: 'setSelectedBranch', + name: branch.name || ' ', + id: seriesId, + team: branch.team || ' ', + }); + try { + const { last_build } = branch; + const buildUrl = `/data/series/${seriesId}/builds/${last_build}/info?`; + const res = await fetch(buildUrl); + const json = await res.json(); + const buildData = json.build; + + dispatch({ type: 'setBuildData', buildData }); + } catch (error) { + dispatch({ type: 'setErrorState', errorState: error }); + } + }; + + fetchData(); + } + return () => { + dispatch({ type: 'flushParentData' }); + }; + }, [seriesId, branchesState]); + + return ( + <> + + {t('series.all_builds')} + + + + + + + {t('series.stability_table')} + + + + ); +}; + +export default Series; diff --git a/frontend/src/components/overview/Series.styles.js b/frontend/src/components/overview/Series.styles.js new file mode 100644 index 00000000..1e552a2b --- /dev/null +++ b/frontend/src/components/overview/Series.styles.js @@ -0,0 +1,14 @@ +import styled from 'styled-components'; +import { overviewElement } from '../../styles/baseComponents'; + +export const ChartContainer = styled(overviewElement)` + margin: 20px 40px 40px 0; + background-color: var(--nero-white); + min-width: ${props => props.minWidth}; +`; + +export const ElementHeader = styled.h3` + text-align: center; + margin: 10px; + font-family: 'Space Mono'; +`; diff --git a/frontend/src/components/parentData/ParentBuild.js b/frontend/src/components/parentData/ParentBuild.js deleted file mode 100644 index 268931c3..00000000 --- a/frontend/src/components/parentData/ParentBuild.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, { useEffect } from 'react'; -import { useParams } from 'react-router'; -import { useStateValue } from '../../contexts/state'; -import { buildTypes, suiteTypes } from '../../utils/parentDataTypes'; - -import ParentTable from './ParentTable'; - -const ParentSeries = () => { - const { seriesId, buildId, testId } = useParams(); - const [ - { - parentData: { buildData } - }, - dispatch - ] = useStateValue(); - - useEffect(() => { - const url = `/data/series/${seriesId}/builds/${buildId}/info?`; - - const fetchData = async () => { - // dispatch({ type: 'setLoadingState', loadingState: true }); - try { - const res = await fetch(url); - const json = await res.json(); - const buildData = json.build; - dispatch({ type: 'setBuildData', buildData }); - // dispatch({ type: 'setLoadingState', loadingState: false }); - } catch (error) { - dispatch({ type: 'setErrorState', errorState: error }); - } - }; - fetchData(); - }, [dispatch, seriesId, buildId]); - - const types = testId ? suiteTypes : buildTypes; - - return ; -}; - -export default ParentSeries; diff --git a/frontend/src/components/parentData/ParentBuild.jsx b/frontend/src/components/parentData/ParentBuild.jsx new file mode 100644 index 00000000..fd5015a5 --- /dev/null +++ b/frontend/src/components/parentData/ParentBuild.jsx @@ -0,0 +1,55 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { useStateValue } from '../../contexts/state'; +import { buildTypes } from '../../utils/parentDataTypes'; + +import ParentTable from './ParentTable'; + +const ParentSeries = () => { + const { seriesId, buildId } = useParams(); + const [ + { + parentData: { buildData }, + branchesState, + }, + dispatch, + ] = useStateValue(); + + useEffect(() => { + if (branchesState) { + const branch = branchesState.series?.find( + ({ id: serie_id }) => serie_id === parseInt(seriesId, 10) + ); + const fetchData = async () => { + dispatch({ type: 'setSeriesData', seriesData: branch }); + dispatch({ + type: 'setSelectedBranch', + name: branch.name || ' ', + id: seriesId, + team: branch.team || ' ', + }); + try { + const url = `/data/series/${seriesId}/builds/${buildId}/info?`; + const res = await fetch(url); + const json = await res.json(); + const buildData = json.build; + + dispatch({ type: 'setBuildData', buildData }); + } catch (error) { + dispatch({ type: 'setErrorState', errorState: error }); + } + }; + + fetchData(); + } + // returned function will be called on component unmount + return () => { + dispatch({ type: 'flushParentData' }); + }; + }, [seriesId, buildId, branchesState]); + + return ; +}; + +export default ParentSeries; diff --git a/frontend/src/components/parentData/ParentSeries.js b/frontend/src/components/parentData/ParentSeries.js deleted file mode 100644 index 91ccc890..00000000 --- a/frontend/src/components/parentData/ParentSeries.js +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useEffect } from 'react'; -import { useParams } from 'react-router'; -import { useStateValue } from '../../contexts/state'; -import { seriesTypes } from '../../utils/parentDataTypes'; - -import ParentTable from './ParentTable'; - -const ParentSeries = () => { - const { seriesId } = useParams(); - - const [ - { - parentData: { seriesData } - }, - dispatch - ] = useStateValue(); - - useEffect(() => { - const url = `/data/series/${seriesId}/info?`; - - const fetchData = async () => { - try { - const res = await fetch(url); - const json = await res.json(); - const seriesData = json.series; - dispatch({ type: 'setSeriesData', seriesData }); - } catch (error) { - dispatch({ type: 'setErrorState', errorState: error }); - } - }; - fetchData(); - }, [seriesId, dispatch]); - - return ; -}; - -export default ParentSeries; diff --git a/frontend/src/components/parentData/ParentSeries.jsx b/frontend/src/components/parentData/ParentSeries.jsx new file mode 100644 index 00000000..cc1e645c --- /dev/null +++ b/frontend/src/components/parentData/ParentSeries.jsx @@ -0,0 +1,56 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import React, { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { useStateValue } from '../../contexts/state'; +import { buildTypes } from '../../utils/parentDataTypes'; + +import ParentTable from './ParentTable'; + +const ParentSeries = () => { + const { seriesId } = useParams(); + + const [ + { + parentData: { buildData }, + branchesState, + }, + dispatch, + ] = useStateValue(); + + useEffect(() => { + if (branchesState) { + const branch = branchesState.series?.find( + ({ id: serie_id }) => serie_id === parseInt(seriesId, 10) + ); + const fetchData = async () => { + dispatch({ type: 'setSeriesData', seriesData: branch }); + dispatch({ + type: 'setSelectedBranch', + name: branch.name || ' ', + id: seriesId, + team: branch.team || ' ', + }); + try { + const { last_build } = branch; + const buildUrl = `/data/series/${seriesId}/builds/${last_build}/info?`; + const res = await fetch(buildUrl); + const json = await res.json(); + const buildData = json.build; + + dispatch({ type: 'setBuildData', buildData }); + } catch (error) { + dispatch({ type: 'setErrorState', errorState: error }); + } + }; + + fetchData(); + } + return () => { + dispatch({ type: 'flushParentData' }); + }; + }, [seriesId, branchesState]); + + return ; +}; + +export default ParentSeries; diff --git a/frontend/src/components/parentData/ParentTable.js b/frontend/src/components/parentData/ParentTable.js deleted file mode 100644 index 60b44580..00000000 --- a/frontend/src/components/parentData/ParentTable.js +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import * as R from 'ramda'; -import ThemeContext from '../../contexts/themeContext'; - -const ParentTable = props => { - const theme = useContext(ThemeContext); - - const tableStyles = css` - ${theme.baseTableStyle} - table { - border-collapse: collapse; - table-layout: auto; - overflow: auto; - max-width: 400px; - } - - td, - th { - vertical-align: middle; - } - td:first-of-type { - vertical-align: middle; - } - `; - - const { data, types } = props; - - const headerRow = () => { - return types.map(name => { - const CapitalCaseInitial = - name.charAt(0).toUpperCase() + name.slice(1); - return {CapitalCaseInitial}; - }); - }; - - const bodyRow = () => { - const bodyValues = R.props(types, data); - - return bodyValues.map((value, index) => { - return {value}; - }); - }; - - return ( - - {data && ( -
- - - {headerRow()} - - - {bodyRow()} - -
-
- )} -
- ); -}; - -ParentTable.propTypes = { - data: PropTypes.object, - types: PropTypes.array.isRequired -}; - -export default ParentTable; diff --git a/frontend/src/components/parentData/ParentTable.jsx b/frontend/src/components/parentData/ParentTable.jsx new file mode 100644 index 00000000..62851b54 --- /dev/null +++ b/frontend/src/components/parentData/ParentTable.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { pick } from 'ramda'; +import { capitalCaseInitial, removeUnderscore } from '../../utils/helpers'; +import { + Container, + ParagraphContainer, + StatusSpan, +} from './ParentTable.styles'; + +const ParentTable = props => { + const { data, types } = props; + + const cleansedData = data && pick(types, data); + + const showData = () => { + return Object.entries(cleansedData).map(([key, value]) => { + const cleanedHeader = removeUnderscore(capitalCaseInitial(key)); + + return ( + + {cleanedHeader}: + {key.includes('status') ? ( + {value} + ) : ( + {value} + )} + + ); + }); + }; + + return ( + + {data && {showData()}} + + ); +}; + +ParentTable.propTypes = { + data: PropTypes.object, + types: PropTypes.array.isRequired, +}; + +export default ParentTable; diff --git a/frontend/src/components/parentData/ParentTable.styles.js b/frontend/src/components/parentData/ParentTable.styles.js new file mode 100644 index 00000000..a2682fb7 --- /dev/null +++ b/frontend/src/components/parentData/ParentTable.styles.js @@ -0,0 +1,25 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + background: var(--hermanni-grey); + padding: 40px 8px 40px 8px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + width: 100%; +`; + +export const ParagraphContainer = styled.p` + display: flex; + padding: 0 10px; + + span:first-child { + padding-right: 10px; + font-weight: bolder; + } +`; + +export const StatusSpan = styled.span` + color: ${props => + props.status === 'PASS' ? 'var(--pirlo-blue)' : 'var(--nelson-purple)'}; +`; diff --git a/frontend/src/components/suite/LogMessages.jsx b/frontend/src/components/suite/LogMessages.jsx new file mode 100644 index 00000000..102c2a30 --- /dev/null +++ b/frontend/src/components/suite/LogMessages.jsx @@ -0,0 +1,64 @@ +import React from 'react'; +import SuiteLogMessage from './SuiteLogMessage'; +import { ReactComponent as Fail } from '../../images/fail-white.svg'; +import { + SelectedTestContainer, + FlexGrowColumn, + LogRow, + InfoLevel, +} from './LogMessages.styles'; + +const LogMessages = ({ test }) => { + return test ? ( + + + + + + + + + + + + {test.log_messages !== null && + test.log_messages.map( + ({ log_level, message, timestamp }, i) => { + return ( + + +
+ {log_level} +
+ + + +
+
+ + + ); + } + )} + +
LevelLog messageTimestamp
+ + +
+ {timestamp} +
+
+
+
+ ) : null; +}; + +export default LogMessages; diff --git a/frontend/src/components/suite/LogMessages.styles.js b/frontend/src/components/suite/LogMessages.styles.js new file mode 100644 index 00000000..69f7fd94 --- /dev/null +++ b/frontend/src/components/suite/LogMessages.styles.js @@ -0,0 +1,77 @@ +import styled from 'styled-components'; + +export const SelectedTestContainer = styled.div` + background: #fff; + margin-top: 5px; + padding: 5px; + border-radius: 5px; + display: flex; + flex-direction: row; + table { + border-collapse: collapse; + table-layout: fixed; + width: 100%; + border: 1px solid var(--hermanni-grey); + } + thead { + background: var(--hermanni-grey); + text-align: left; + + th { + color: var(--gradient-black); + font-weight: bold; + } + + th:first-child { + padding-left: 8px; + } + } + .table-item { + padding: 0.25rem 0rem; + white-space: normal; + overflow: hidden; + } + + tbody tr { + border-bottom: 1px solid #eee; + } + + tbody td:first-child { + padding-left: 8px; + } + + .tableLogLevel { + width: 10%; + } + .tableMessage { + width: 70%; + } + .tableTimeStamp { + width: 20%; + } +`; + +export const FlexGrowColumn = styled.div` + flex-grow: 1; + display: flex; + flex-direction: column; + padding: 5px; +`; + +export const LogRow = styled.tr` + background: ${props => + props.log_level === 'FAIL' && 'var(--nelson-purple)'}; + color: ${props => props.log_level === 'FAIL' && 'var(--nero-white)'}; +`; + +export const InfoLevel = styled.td` + display: flex; + justify-content: space-between; + align-items: center; + + span { + position: relative; + top: -2px; + left: -16px; + } +`; diff --git a/frontend/src/components/suite/SuiteLogMessage.jsx b/frontend/src/components/suite/SuiteLogMessage.jsx new file mode 100644 index 00000000..d4e69d1b --- /dev/null +++ b/frontend/src/components/suite/SuiteLogMessage.jsx @@ -0,0 +1,23 @@ +import React, { useState } from 'react'; +import { TestMessage } from './SuiteLogMessage.styles'; + +const SuiteLogMessage = ({ message }) => { + const [isOpen, setIsopen] = useState(false); + + return ( +
+ setIsopen(!isOpen)} + onKeyDown={() => setIsopen(!isOpen)} + open={isOpen} + > + {message} + +
+ ); +}; + +export default SuiteLogMessage; diff --git a/frontend/src/components/suite/SuiteLogMessage.styles.js b/frontend/src/components/suite/SuiteLogMessage.styles.js new file mode 100644 index 00000000..a6a4c013 --- /dev/null +++ b/frontend/src/components/suite/SuiteLogMessage.styles.js @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +export const TestMessage = styled.div` + padding: 0.25rem 0rem; + white-space: nowrap; + text-overflow: ellipsis; + width: 100%; + overflow: hidden; + white-space: ${props => (props.open ? 'normal' : 'nowrap')}; + :hover { + cursor: pointer; + } +`; diff --git a/frontend/src/components/suite/Testlist.jsx b/frontend/src/components/suite/Testlist.jsx new file mode 100644 index 00000000..b2089b72 --- /dev/null +++ b/frontend/src/components/suite/Testlist.jsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import { useParams } from 'react-router'; +import { pickIcon } from '../TestIcon'; +import { + FlexContainer, + HeaderContainer, + SvgCollection, + SvgDown, + SvgUp, + TestListContainer, + DotSpan, + TestStatusRow, + StyledLink, + SvgStatus, + TimeContainer, + TagContainer, + Tag, +} from './Testlist.styles'; + +const Testlist = ({ suite }) => { + const { suiteId, buildId, seriesId, testId } = useParams(); + const [Open, setOpen] = useState(true); + + return ( + + setOpen(!Open)} + onKeyPress={() => setOpen(!Open)} + > + +

{suite.name} Tests

+

+ {suite.tests.length} test + {suite.tests.length > 1 && 's'} +

+ {Open ? : } +
+ +
    + {' '} + {suite.tests.map((test, i) => { + return ( +
  • + + + {' '} + + {test.name} + + + {pickIcon(test.status)} + + + {(test.elapsed / 1000).toFixed(2)}s + + + {' '} + {test.tags.map((tag, i) => { + return {tag}; + })} + + +
  • + ); + })} +
+
+
+ ); +}; + +export default Testlist; diff --git a/frontend/src/components/suite/Testlist.styles.js b/frontend/src/components/suite/Testlist.styles.js new file mode 100644 index 00000000..a9c5c389 --- /dev/null +++ b/frontend/src/components/suite/Testlist.styles.js @@ -0,0 +1,176 @@ +import React from 'react'; +import styled from 'styled-components'; +import { NavLink } from 'react-router-dom'; +import { ReactComponent as Collection } from '../../images/collection-white.svg'; +import { ReactComponent as Up } from '../../images/chevron-up-white.svg'; +import { ReactComponent as Down } from '../../images/chevron-down-white.svg'; + +export const FlexContainer = styled.div` + display: flex; + flex-direction: column; + padding: 40px 0px; + :hover { + cursor: pointer; + } + + .Open, + .Close { + max-height: 0; + overflow-y: hidden; + } + + .Open { + max-height: 100%; + } +`; + +export const HeaderContainer = styled.div` + background: var(--titan-green); + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-between; + border-radius: 48px; + width: 100%; + color: var(--nero-white); + + h3 { + font-size: 20px; + font-family: 'Space Mono'; + letter-spacing: -0.04em; + font-style: normal; + font-weight: normal; + flex: 20; + padding-left: 56px; + } + + p { + flex: 2; + } +`; + +export const SvgCollection = styled(Collection)` + position: relative; + left: 24px; +`; + +export const SvgDown = styled(Down)` + color: white; + flex: 1; +`; + +export const SvgUp = styled(Up)` + color: white; + flex: 1; +`; + +export const TestListContainer = styled.div` + padding-left: 24px; + + ul { + list-style: none; + margin: 0; + padding: 0; + margin-left: 10px; + } + + ul li { + margin: 0; + padding: 0 7px; + border-left: 1px solid var(--tonic-grey); + } + + ul li:last-child { + border-left: none; + } + + ul li:before { + position: relative; + top: -0.3em; + height: 2.4em; + width: 32px; + color: white; + border-bottom: 1px solid var(--tonic-grey); + content: ''; + display: inline-block; + left: -7px; + } + ul li:first-child:before { + height: 3.2em; + } + + ul li:last-child:before { + border-left: 1px solid var(--tonic-grey); + width: 33px; + } +`; + +export const DotSpan = styled.span` + height: 9px; + width: 9px; + background-color: ${props => + props.isselected ? 'var(--pirlo-blue)' : 'var(--tonic-grey)'}; + border-radius: 50%; + display: inline-block; + position: relative; + left: -7px; +`; + +export const TestStatusRow = styled.div` + display: inline-flex; + flex-direction: row; + width: 80%; + align-items: center; + + span { + color: var(--evidence-grey); + } +`; + +// eslint-disable-next-line no-unused-vars +export const StyledLink = styled(({ isselected, ...props }) => ( + +))` + padding: 4px; + font-weight: bolder; + cursor: pointer; + display: inline; + text-decoration: none; + flex: 2; + color: ${props => props.isselected && 'var(--pirlo-blue) !important'}; + + :hover { + background: var(--tonic-grey); + } +`; + +export const SvgStatus = styled.span` + position: relative; + top: -2px; + left: 8px; + flex: 0.5; +`; + +export const TimeContainer = styled.span` + flex: 0.5; +`; + +export const TagContainer = styled.span` + flex: 3; + + @media only screen and (max-width: 1300px) { + flex: 2; + } + + @media only screen and (max-width: 1024px) { + flex: 1; + } +`; + +export const Tag = styled.span` + border: 1px solid var(--evidence-grey); + padding: 0 8px; + border-radius: 16px; + font-size: 10px; + margin: 0 8px; +`; diff --git a/frontend/src/contexts/reducer.js b/frontend/src/contexts/reducer.js index bc773e78..b0d69558 100644 --- a/frontend/src/contexts/reducer.js +++ b/frontend/src/contexts/reducer.js @@ -3,68 +3,78 @@ const reducer = (state, action) => { case 'updateHistory': return { ...state, - historyDataState: action.historyData + historyDataState: action.historyData, }; case 'setAmountOfBuilds': return { ...state, - amountOfBuilds: action.amountOfBuilds + amountOfBuilds: action.amountOfBuilds, + }; + case 'setAmountShown': + return { + ...state, + amountShown: action.amount, + }; + case 'setFailureList': + return { + ...state, + failureList: action.failures, }; case 'setLoadingState': return { ...state, - loadingState: action.loadingState + loadingState: action.loadingState, }; case 'setErrorState': return { ...state, - errorState: action.errorState + errorState: action.errorState, }; case 'setHistoryFilterType': return { ...state, historyFilter: { filterType: action.filterType, - isChecked: action.isChecked - } + isChecked: action.isChecked, + }, }; case 'setHistoryFilterPass': return { ...state, historyFilterPass: { filterType: action.filterType, - isChecked: action.isChecked - } + isChecked: action.isChecked, + }, }; case 'setHistoryFilterFail': return { ...state, historyFilterFail: { filterType: action.filterType, - isChecked: action.isChecked - } + isChecked: action.isChecked, + }, }; case 'setLastRunFilterFail': return { ...state, lastRunFilterFail: { filterType: action.filterType, - isChecked: action.isChecked - } + isChecked: action.isChecked, + }, }; case 'setLastRunFilterPass': return { ...state, lastRunFilterPass: { filterType: action.filterType, - isChecked: action.isChecked - } + isChecked: action.isChecked, + }, }; case 'setBranches': return { ...state, - branchesState: action.branches + branchesState: action.branches, }; case 'setSelectedBranch': return { @@ -72,45 +82,89 @@ const reducer = (state, action) => { selectedBranchState: { name: action.name, id: action.id, - team: action.team - } + team: action.team, + }, }; case 'setMetadata': return { ...state, - metadataState: action.metadata + metadataState: action.metadata, }; case 'setSelectedBuild': return { ...state, - selectedBuildState: action.selectedBuild + selectedBuildState: action.selectedBuild, }; case 'setTeams': return { ...state, - teamsState: action.teams + teamsState: action.teams, }; case 'setSelectedSuiteState': return { ...state, - selectedSuiteState: action.suite + selectedSuiteState: action.suite, + }; + + case 'setTestStabilityList': + return { + ...state, + testStabilityList: action.data, + }; + case 'setStabilityChecker': + return { + ...state, + stabilityChecker: action.setStability, }; case 'setSeriesData': return { ...state, parentData: { ...state.parentData, - seriesData: action.seriesData - } + seriesData: action.seriesData, + }, + }; + case 'setOffset': + return { + ...state, + offset: action.offset, }; case 'setBuildData': return { ...state, parentData: { ...state.parentData, - buildData: action.buildData - } + buildData: action.buildData, + }, + }; + case 'flushHistory': + return { + ...state, + historyDataState: null, }; + case 'flushMetadata': + return { + ...state, + metadataState: [], + }; + case 'flushParentData': + return { + ...state, + parentData: { + buildData: null, + seriesData: { + last_build_id: '', + last_status: '', + last_started: '', + }, + }, + }; + case 'flushSuiteState': + return { + ...state, + selectedSuiteState: null, + }; + default: return state; } diff --git a/frontend/src/contexts/state.js b/frontend/src/contexts/state.js index 1bb3b64e..bc758ebb 100644 --- a/frontend/src/contexts/state.js +++ b/frontend/src/contexts/state.js @@ -7,23 +7,33 @@ const initialState = { loadingState: false, errorState: null, amountOfBuilds: 10, + amountShown: 10, + offset: 0, amountFilteredData: null, lastRunFilterFail: { isChecked: false, - filterType: '' + filterType: '', }, lastRunFilterPass: { isChecked: false, - filterType: '' + filterType: '', }, branchesState: null, selectedBranchState: { name: 'All builds', id: 1 }, metadataState: [], + testStabilityList: [], + failureList: [], selectedBuildState: {}, + selectedSuiteState: null, + stabilityChecker: 'unstable', parentData: { - seriesData: null, - buildData: null - } + seriesData: { + last_build_id: '', + last_status: '', + last_started: '', + }, + buildData: null, + }, }; export const StateProvider = ({ reducer, children }) => { diff --git a/frontend/src/hooks/useMetadata.jsx b/frontend/src/hooks/useMetadata.jsx new file mode 100644 index 00000000..7bd36900 --- /dev/null +++ b/frontend/src/hooks/useMetadata.jsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { useStateValue } from '../contexts/state'; + +const useMetadata = () => { + const [{ selectedBranchState, branchesState }, dispatch] = useStateValue(); + let { buildId, seriesId } = useParams(); + + const branch_id = seriesId || selectedBranchState; + + useEffect(() => { + const fetchData = async () => { + dispatch({ type: 'setLoadingState', loadingState: true }); + if (branch_id && buildId) { + try { + const res = await fetch( + `/data/series/${branch_id}/builds/${buildId}/metadata`, + {} + ); + const json = await res.json(); + dispatch({ type: 'setLoadingState', loadingState: false }); + dispatch({ + type: 'setMetadata', + metadata: json, + }); + } catch (error) { + dispatch({ type: 'setErrorState', errorState: error }); + } + } + }; + if (branchesState) { + fetchData(); + } + + // returned function will be called on component unmount + return () => { + dispatch({ type: 'flushMetadata' }); + }; + }, [branch_id, branchesState, buildId, dispatch]); +}; + +export default useMetadata; diff --git a/frontend/src/images/caret-down.svg b/frontend/src/images/caret-down.svg new file mode 100644 index 00000000..6b286c46 --- /dev/null +++ b/frontend/src/images/caret-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/caret-up.svg b/frontend/src/images/caret-up.svg new file mode 100644 index 00000000..83fbbb32 --- /dev/null +++ b/frontend/src/images/caret-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/checked.svg b/frontend/src/images/checked.svg new file mode 100644 index 00000000..144860cb --- /dev/null +++ b/frontend/src/images/checked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-down-white.svg b/frontend/src/images/chevron-down-white.svg new file mode 100644 index 00000000..4d3f3a7c --- /dev/null +++ b/frontend/src/images/chevron-down-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-down.svg b/frontend/src/images/chevron-down.svg new file mode 100644 index 00000000..f399749e --- /dev/null +++ b/frontend/src/images/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-left.svg b/frontend/src/images/chevron-left.svg new file mode 100644 index 00000000..46d774ab --- /dev/null +++ b/frontend/src/images/chevron-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-right.svg b/frontend/src/images/chevron-right.svg new file mode 100644 index 00000000..a4755841 --- /dev/null +++ b/frontend/src/images/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-up-white.svg b/frontend/src/images/chevron-up-white.svg new file mode 100644 index 00000000..62a22481 --- /dev/null +++ b/frontend/src/images/chevron-up-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-up.svg b/frontend/src/images/chevron-up.svg new file mode 100644 index 00000000..7279b25b --- /dev/null +++ b/frontend/src/images/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/chevron-verticalbar-left.svg b/frontend/src/images/chevron-verticalbar-left.svg new file mode 100644 index 00000000..ab8e2780 --- /dev/null +++ b/frontend/src/images/chevron-verticalbar-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/collection-closed.svg b/frontend/src/images/collection-closed.svg new file mode 100644 index 00000000..814e475a --- /dev/null +++ b/frontend/src/images/collection-closed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/collection-open.svg b/frontend/src/images/collection-open.svg new file mode 100644 index 00000000..8d9e97d8 --- /dev/null +++ b/frontend/src/images/collection-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/collection-white.svg b/frontend/src/images/collection-white.svg new file mode 100644 index 00000000..13a57556 --- /dev/null +++ b/frontend/src/images/collection-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/default.svg b/frontend/src/images/default.svg new file mode 100644 index 00000000..a52afd22 --- /dev/null +++ b/frontend/src/images/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/fail-white.svg b/frontend/src/images/fail-white.svg new file mode 100644 index 00000000..e7bec8df --- /dev/null +++ b/frontend/src/images/fail-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/fail.svg b/frontend/src/images/fail.svg new file mode 100644 index 00000000..cd928905 --- /dev/null +++ b/frontend/src/images/fail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/indeterminate.svg b/frontend/src/images/indeterminate.svg new file mode 100644 index 00000000..f90fc4cd --- /dev/null +++ b/frontend/src/images/indeterminate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/not-found.svg b/frontend/src/images/not-found.svg new file mode 100644 index 00000000..e2b1d104 --- /dev/null +++ b/frontend/src/images/not-found.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/skip.svg b/frontend/src/images/skip.svg new file mode 100644 index 00000000..3ed80c2a --- /dev/null +++ b/frontend/src/images/skip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/success.svg b/frontend/src/images/success.svg new file mode 100644 index 00000000..3d9747af --- /dev/null +++ b/frontend/src/images/success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/unchecked.svg b/frontend/src/images/unchecked.svg new file mode 100644 index 00000000..eefc1b7d --- /dev/null +++ b/frontend/src/images/unchecked.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 27f9a63d..c9eaa884 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -21,12 +21,19 @@ html { ----------------------*/ :root { --nelson-purple: #b8557b; - --whipped-red: #c63757; - --pirlo-blue: #56a2c3; - --revolution-black: #141312; - --dove-grey: #7b756f; - --mithril-grey: #e9e8e8; - --powder-white: #ffffff; + --semolina-red: #c63757; + --pirlo-blue: #067cc1; + --titan-green: #238564; + --gradient-black: #141312; + --evidence-grey: #7b756f; + --hermanni-grey: #edecec; + --toukola-green: #e9f5e1; + --kumpula-yellow: #faf3e1; + --vallila-blue: #e1f1f7; + --arabia-red: #faecec; + --nero-white: #ffffff; + --tonic-grey: #d7d4d3; + --sparkling-blue: #80bfdb; } /** Basics **/ @@ -36,6 +43,7 @@ body { font-size: 14px; line-height: 24px; } + h1, h2, h3, @@ -59,6 +67,21 @@ h4 { font-size: 16px; } +svg { + vertical-align: middle; +} + +button:focus, +input:focus, +a:focus { + outline: 2px solid var(--pirlo-blue); +} + +button:hover:enabled, +a:hover { + cursor: pointer; +} + /** a11y **/ .sr-show { diff --git a/frontend/src/locales/en/buttons.json b/frontend/src/locales/en/buttons.json new file mode 100644 index 00000000..72b8fe6a --- /dev/null +++ b/frontend/src/locales/en/buttons.json @@ -0,0 +1,7 @@ +{ + "hide_tests": { + "header": "Hide tests that are", + "passing": "Passing", + "failing": "Failing" + } +} diff --git a/frontend/src/locales/en/frontpage.json b/frontend/src/locales/en/frontpage.json index 6bee1d1d..362a3c37 100644 --- a/frontend/src/locales/en/frontpage.json +++ b/frontend/src/locales/en/frontpage.json @@ -29,14 +29,17 @@ }, "icons": { "title": "Icons", - "opening_paragraph": "Icons to indicate test status use green/red/grey coloring combined with helping shapes. If you have trouble viewing the results, please create an accessibility issue in the repository. Our goal is to make this project accessible for everyone." + "opening_paragraph": "Icons to indicate test status use blue and red coloring combined with helping shapes. If you have trouble viewing the results, please create an accessibility issue in the repository. Our goal is to make this project accessible for everyone.", + "pass": "Passed test", + "fail": "Failed test", + "skipped": "Skipped test (result file found)", + "not_found": "Test not found in selected run" }, "views": { "title": "Different Views", "history": "History: Shows how the recent test runs have executed. Amount of visible runs can be 5-30 (default 10).", "team": "Team: Display all series related to a single team.", "suite": "Suite: Collection of different test cases. Open different test cases and see related results, log messages and metadata." - }, "feedback": { "title": "How to give feedback?", @@ -57,4 +60,4 @@ "link_text": "Apache 2.0 license" } } -} \ No newline at end of file +} diff --git a/frontend/src/locales/en/header.json b/frontend/src/locales/en/header.json new file mode 100644 index 00000000..776cb4cb --- /dev/null +++ b/frontend/src/locales/en/header.json @@ -0,0 +1,13 @@ +{ + "Build": "Build", + "build": "build", + "Series": "Series", + "series": "series", + "from": "from", + "suite": "Suite", + "in": "in", + "buttons": { + "overview": "Overview", + "history": "History" + } +} diff --git a/frontend/src/locales/en/history.json b/frontend/src/locales/en/history.json new file mode 100644 index 00000000..f8585c48 --- /dev/null +++ b/frontend/src/locales/en/history.json @@ -0,0 +1,16 @@ +{ + "series": {}, + "build": { + "table": { + "suite": "Suitename", + "status": "Status", + "test": "Test(s)", + "error": "Error messages", + "time": "Time", + "flakiness": "Flakiness", + "row": { + "build": "build" + } + } + } +} diff --git a/frontend/src/locales/en/mainnav.json b/frontend/src/locales/en/mainnav.json index 4a618e7b..7c0cfa8b 100644 --- a/frontend/src/locales/en/mainnav.json +++ b/frontend/src/locales/en/mainnav.json @@ -1,8 +1,15 @@ { - "logo": "Epimetheus", - "help": "Help", - "history": "History", - "team": "Team", - "github": "GitHub", - "version": "Version" -} \ No newline at end of file + "logo": "EPIMETHEUS", + "help": "About", + "history": "History", + "team": "Teams", + "github": "GitHub", + "version": "Version", + "footer": { + "E": "E", + "epimetheus": "Epimetheus", + "version": "Version", + "powered": "is powered by", + "siili": "Siili" + } +} diff --git a/frontend/src/locales/en/metadata.json b/frontend/src/locales/en/metadata.json new file mode 100644 index 00000000..904d2d87 --- /dev/null +++ b/frontend/src/locales/en/metadata.json @@ -0,0 +1,5 @@ +{ + "metadata": "Metadata", + "name": "Name", + "value": "Value" +} diff --git a/frontend/src/locales/en/overview.json b/frontend/src/locales/en/overview.json new file mode 100644 index 00000000..215d7b3c --- /dev/null +++ b/frontend/src/locales/en/overview.json @@ -0,0 +1,11 @@ +{ + "series": { + "all_builds": "All Build History", + "stability_table": "Stability Table", + "unstable_tests": "Suites with unstable tests" + }, + "build": { + "test": "Test status", + "suite": "Suite status" + } +} diff --git a/frontend/src/locales/en/parentData.json b/frontend/src/locales/en/parentData.json new file mode 100644 index 00000000..63e5d36f --- /dev/null +++ b/frontend/src/locales/en/parentData.json @@ -0,0 +1,3 @@ +{ + "title": "Last Build Information" +} diff --git a/frontend/src/pages/Build.js b/frontend/src/pages/Build.jsx similarity index 63% rename from frontend/src/pages/Build.js rename to frontend/src/pages/Build.jsx index 1b268f11..9e0b4284 100644 --- a/frontend/src/pages/Build.js +++ b/frontend/src/pages/Build.jsx @@ -1,57 +1,31 @@ // eslint-disable-next-line import React, { Fragment, useEffect } from 'react'; -import Table from '../components/lastRunTable/Table'; -import LastRunCheckBox from '../components/LastRunCheckbox'; +import Table from '../components/buildTable/Table'; +import LastRunCheckBox from '../components/buttons/LastRunCheckbox'; import { useStateValue } from '../contexts/state'; -import MetadataTable from '../components/lastRunTable/MetadataTable'; import { useParams } from 'react-router'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; import BreadcrumbNav from '../components/BreadcrumbNav'; import ParentBuild from '../components/parentData/ParentBuild'; import Loading from '../components/Loading'; +import Header from '../components/header/Header'; +import BuildMetadata from '../components/metadata/BuildMetadata'; +import useMetadata from '../hooks/useMetadata'; +import { + ParentInfoContainer, + LastRunContainer, + TableHeader, +} from './Build.styles'; const Build = () => { - const buildStyles = css` - position: relative; - margin-top: 10px; - .filter-container, - .parentInfo-container { - display: flex; - } - - .parentInfo-container { - padding: 20px 0; - } - `; const [ { loadingState, historyDataState, selectedBranchState, branchesState }, - dispatch + dispatch, ] = useStateValue(); let { buildId, seriesId } = useParams(); const branch_id = seriesId || selectedBranchState; useEffect(() => { - const fetchData = async () => { - dispatch({ type: 'setLoadingState', loadingState: true }); - if (branch_id && buildId) { - try { - const res = await fetch( - `/data/series/${branch_id}/builds/${buildId}/metadata`, - {} - ); - const json = await res.json(); - dispatch({ type: 'setLoadingState', loadingState: false }); - dispatch({ - type: 'setMetadata', - metadata: json - }); - } catch (error) { - //console.log(error); - } - } - }; const fetchHistoryData = async () => { dispatch({ type: 'setLoadingState', loadingState: true }); if (branch_id && buildId) { @@ -62,7 +36,7 @@ const Build = () => { type: 'setSelectedBranch', name: branch?.name, id: branch_id, - team: branch?.team || ' ' + team: branch?.team || ' ', }); dispatch({ type: 'setSelectedBuild', selectedBuild: buildId }); try { @@ -74,7 +48,7 @@ const Build = () => { dispatch({ type: 'setLoadingState', loadingState: false }); dispatch({ type: 'updateHistory', - historyData: json + historyData: json, }); } catch (error) { dispatch({ type: 'setErrorState', errorState: error }); @@ -83,13 +57,14 @@ const Build = () => { }; if (branchesState) { fetchHistoryData(); - fetchData(); } }, [dispatch, branch_id, buildId, branchesState]); + useMetadata(); + return ( -
-
+
+ {!historyDataState || loadingState ? (
{ Content loaded.
-
+
+ -
- - + + + Test results for build {buildId} + )} diff --git a/frontend/src/pages/Build.styles.js b/frontend/src/pages/Build.styles.js new file mode 100644 index 00000000..10756e8c --- /dev/null +++ b/frontend/src/pages/Build.styles.js @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +export const ParentInfoContainer = styled.div` + display: flex; + padding: 20px 0; +`; + +export const LastRunContainer = styled.div` + position: relative; + margin-top: 10px; +`; + +export const TableHeader = styled.h2` + padding-top: 40px; +`; diff --git a/frontend/src/pages/Dashboard.js b/frontend/src/pages/Dashboard.js deleted file mode 100644 index 3651402f..00000000 --- a/frontend/src/pages/Dashboard.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { useParams } from 'react-router'; -import BreadcrumbNav from '../components/BreadcrumbNav'; - -const Dashboard = () => { - const { buildId } = useParams(); - - const correctStatus = () => (buildId ? 'build' : 'series'); - - return ( -
- - Dashboard -
- ); -}; - -export default Dashboard; diff --git a/frontend/src/pages/Frontpage.js b/frontend/src/pages/Frontpage.jsx similarity index 70% rename from frontend/src/pages/Frontpage.js rename to frontend/src/pages/Frontpage.jsx index 8714322f..fa76ba63 100644 --- a/frontend/src/pages/Frontpage.js +++ b/frontend/src/pages/Frontpage.jsx @@ -1,17 +1,16 @@ // eslint-disable-next-line import React from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; import { useTranslation } from 'react-i18next'; +import { ReactComponent as Pass } from '../images/success.svg'; +import { ReactComponent as Fail } from '../images/fail.svg'; +import { ReactComponent as Skipped } from '../images/skip.svg'; +import { ReactComponent as NotFound } from '../images/not-found.svg'; +import { FrontPage, IconsContainer } from './Frontpage.styles'; const Frontpage = () => { - const frontpageStyles = css` - max-width: 800px; - width: 100%; - `; const [t] = useTranslation(['frontpage']); return ( -
+

{t('title')}

{t('opening_paragraph')}

@@ -22,7 +21,6 @@ const Frontpage = () => {
  • {t('section.roadmap.ms_202003_1')}
  • {t('section.roadmap.ms_202003_2')}
  • -

    {t('section.terminology.title')}

    {t('section.terminology.opening_paragraph')}

      @@ -46,7 +44,25 @@ const Frontpage = () => {

      {t('section.icons.title')}

      {t('section.icons.opening_paragraph')}

      - icon legend + +
      + {' '} + + {t('section.icons.pass')} +
      +
      + + {t('section.icons.fail')} +
      +
      + + {t('section.icons.skipped')} +
      +
      + + {t('section.icons.not_found')} +
      +

      {t('section.views.title')}

        @@ -80,7 +96,7 @@ const Frontpage = () => { .

        -
    + ); }; diff --git a/frontend/src/pages/Frontpage.styles.js b/frontend/src/pages/Frontpage.styles.js new file mode 100644 index 00000000..bae42ec7 --- /dev/null +++ b/frontend/src/pages/Frontpage.styles.js @@ -0,0 +1,18 @@ +import styled from 'styled-components'; + +export const FrontPage = styled.main` + max-width: 800px; + width: 100%; + margin-left: 40px; +`; + +export const IconsContainer = styled.div` + display: flex; + justify-content: space-around; + padding-top: 20px; + + div { + min-height: 100px; + max-width: 150px; + } +`; diff --git a/frontend/src/pages/History.js b/frontend/src/pages/History.jsx similarity index 64% rename from frontend/src/pages/History.js rename to frontend/src/pages/History.jsx index f77d587f..a5737d21 100644 --- a/frontend/src/pages/History.js +++ b/frontend/src/pages/History.jsx @@ -1,45 +1,35 @@ // eslint-disable-next-line import React, { Fragment, useEffect } from 'react'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; -import Filter from '../components/historyTable/Filter'; +import { useTranslation } from 'react-i18next'; import Table from '../components/historyTable/Table'; import ParentSeries from '../components/parentData/ParentSeries'; -import Checkbox from '../components/Checkbox'; +import Offset from '../components/buttons/OffSetButtons'; +import Checkbox from '../components/buttons/LastRunCheckbox'; +import BuildAmountSelector from '../components/buttons/BuildAmountSelector'; import { useStateValue } from '../contexts/state'; -// import BranchFilter from '../components/BranchFilter'; import { useParams } from 'react-router'; import BreadcrumbNav from '../components/BreadcrumbNav'; import Loading from '../components/Loading'; import { useQueryParams } from '../hooks/useQuery'; +import Header from '../components/header/Header'; +import { + RelativeMain, + FilterContainer, + ParentContainer, +} from './History.styles'; const History = () => { - const filterStyles = css` - position: relative; - - .filter-container, - .parentInfo-container { - display: flex; - flex-flow: row wrap; - } - - .filter-container { - max-width: 800px; - } - - .parentInfo-container { - padding: 20px 0; - } - `; + const [t] = useTranslation(['parentData']); const [ { loadingState, historyDataState, selectedBranchState, amountOfBuilds, - branchesState + branchesState, + offset, }, - dispatch + dispatch, ] = useStateValue(); const { seriesId } = useParams(); const queryParams = useQueryParams(); @@ -47,8 +37,9 @@ const History = () => { const number_of_builds = queryParams.get('numberofbuilds') || amountOfBuilds || '30'; + const total_offset = queryParams.get('offset') || offset; useEffect(() => { - const url = `/data/series/${series_id}/history?builds=${number_of_builds}`; + const url = `/data/series/${series_id}/history?builds=${number_of_builds}&offset=${total_offset}`; if (branchesState) { const branch = branchesState.series?.find( ({ id: serie_id }) => serie_id === parseInt(series_id, 10) @@ -57,20 +48,20 @@ const History = () => { dispatch({ type: 'setLoadingState', loadingState: true }); dispatch({ type: 'setAmountOfBuilds', - amountOfBuilds: number_of_builds + amountOfBuilds: number_of_builds, }); dispatch({ type: 'setSelectedBranch', name: branch?.name || ' ', id: series_id, - team: branch?.team || ' ' + team: branch?.team || ' ', }); try { const res = await fetch(url, {}); const json = await res.json(); dispatch({ type: 'updateHistory', - historyData: json + historyData: json, }); dispatch({ type: 'setLoadingState', loadingState: false }); } catch (error) { @@ -78,21 +69,34 @@ const History = () => { } }; fetchData(); + + // returned function will be called on component unmount + return () => { + dispatch({ type: 'flushHistory' }); + }; } - }, [dispatch, series_id, number_of_builds, branchesState]); + }, [ + dispatch, + series_id, + number_of_builds, + branchesState, + offset, + total_offset, + ]); return ( -
    + - {!loadingState && ( -
    - -
    - )} -
    - - -
    +
    + +

    {t('title')}

    + +
    + + + + + {!historyDataState || loadingState ? ( ) : ( @@ -109,7 +113,7 @@ const History = () => {
    )} - + ); }; diff --git a/frontend/src/pages/History.styles.js b/frontend/src/pages/History.styles.js new file mode 100644 index 00000000..22c1568c --- /dev/null +++ b/frontend/src/pages/History.styles.js @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +export const RelativeMain = styled.main` + position: relative; +`; + +export const HeritanceContainer = styled.div` + display: flex; + flex-flow: row wrap; +`; + +export const FilterContainer = styled(HeritanceContainer)` + justify-content: space-between; + max-width: 700px; +`; + +export const ParentContainer = styled(HeritanceContainer)` + padding: 20px 0; + flex-direction: column; +`; diff --git a/frontend/src/pages/Overview.jsx b/frontend/src/pages/Overview.jsx new file mode 100644 index 00000000..2b4cbaef --- /dev/null +++ b/frontend/src/pages/Overview.jsx @@ -0,0 +1,50 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import React from 'react'; +import { useLocation } from 'react-router-dom'; +import BreadcrumbNav from '../components/BreadcrumbNav'; +import Build from '../components/overview/Build'; +import Series from '../components/overview/Series'; +import ParentBuild from '../components/parentData/ParentBuild'; +import Header from '../components/header/Header'; +import { ParentInfo, FlexDiv, FlexColumn } from './Overview.styles'; +import BuildAmountSelector from '../components/buttons/BuildAmountSelector'; +import Offset from '../components/buttons/OffSetButtons'; + +const Overview = () => { + const pathname = useLocation().pathname; + const buildUrl = pathname.includes('build'); + + const status = buildUrl ? 'build' : 'series'; + + return ( +
    + +
    +
    + + {!buildUrl ? ( + + + + + ) : ( + + )} + + + {buildUrl ? ( + + + + ) : ( + + + + )} + +
    +
    + ); +}; + +export default Overview; diff --git a/frontend/src/pages/Overview.styles.js b/frontend/src/pages/Overview.styles.js new file mode 100644 index 00000000..08814b37 --- /dev/null +++ b/frontend/src/pages/Overview.styles.js @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +export const ParentInfo = styled.div` + display: flex; + flex-flow: row wrap; + padding: 20px 0; +`; + +export const FlexDiv = styled.div` + display: flex; + flex-wrap: wrap; +`; + +export const FlexColumn = styled.div` + display: flex; + justify-content: space-between; + flex-direction: row; + width: 400px; +`; diff --git a/frontend/src/pages/Suite.js b/frontend/src/pages/Suite.js deleted file mode 100644 index 73e62659..00000000 --- a/frontend/src/pages/Suite.js +++ /dev/null @@ -1,338 +0,0 @@ -// eslint-disable-next-line -import React from 'react'; -/** @jsx jsx */ -import { useEffect } from 'react'; -import { useParams } from 'react-router'; -import { useStateValue } from '../contexts/state'; -import theme from '../styles/theme'; -import { css, jsx } from '@emotion/core'; -import FA from 'react-fontawesome'; -import BreadcrumbNav from '../components/BreadcrumbNav'; -import Notfound from '../components/NotFound'; -import { NavLink } from 'react-router-dom'; -import { pickIcon } from '../components/TestIcon'; -import ParentBuild from '../components/parentData/ParentBuild'; -import Loading from '../components/Loading'; - -const Suite = () => { - const { suiteId, buildId, seriesId, testId } = useParams(); - const [ - { - selectedSuiteState, - loadingState, - branchesState, - selectedBranchState - }, - dispatch - ] = useStateValue(); - const branch_id = seriesId || selectedBranchState; - const container = css` - .parentInfo-container { - display: flex; - padding: 20px 0; - } - .container { - display: flex; - } - .fa { - margin-right: 8px; - } - .suiteNav { - list-style: none; - padding: 0; - align-content: center; - border-right: 1px solid grey; - flex-grow: 1; - } - .suiteNav div { - display: flex; - width: 100%; - min-width: 120px; - } - .suiteNav span { - float: right; - margin-left: 12px; - } - .suiteNav a { - width: 100%; - padding: 10px; - color: inherit; - cursor: pointer; - display: inline; - text-decoration: none; - } - .suiteNav a:hover { - background: #ddd; - } - .suiteNav .active { - background: #fff; - } - .suiteMain { - flex-grow: 2; - padding: 10px; - } - `; - useEffect(() => { - const fetchHistoryData = async () => { - if (branch_id && buildId) { - const branch = branchesState.series?.find( - ({ id: serie_id }) => serie_id === parseInt(branch_id, 10) - ); - dispatch({ - type: 'setSelectedBranch', - name: branch?.name, - id: seriesId, - team: branch?.team || ' ' - }); - dispatch({ type: 'setSelectedBuild', selectedBuild: buildId }); - } - }; - const fetchSuiteData = async () => { - dispatch({ type: 'setLoadingState', loadingState: true }); - try { - const res = await fetch( - `/data/series/${seriesId}/builds/${buildId}/suites/${suiteId}/?`, - {} - ); - const json = await res.json(); - dispatch({ type: 'setLoadingState', loadingState: false }); - dispatch({ type: 'setSelectedSuiteState', suite: json }); - } catch (error) { - //console.log(error); - } - }; - if (branchesState) { - fetchHistoryData(); - fetchSuiteData(); - } - }, [dispatch, branch_id, suiteId, buildId, seriesId, branchesState]); - - return ( -
    - {!selectedSuiteState || loadingState ? ( -
    - -
    - ) : selectedSuiteState.suite ? ( -
    -
    - Content loaded. -
    - -
    - -
    -
    -
    - {selectedSuiteState.suite.tests.map((test, i) => { - return ( -
    - - {pickIcon(test.status)} - {test.name} - -
    - ); - })} -
    -
    -
    ID: {selectedSuiteState.suite.id}
    -
    Name: {selectedSuiteState.suite.name}
    -
    - Fullname: {selectedSuiteState.suite.full_name} -
    -
    - Repository:{' '} - {selectedSuiteState.suite.repository} -
    -
    - Testrunid:{' '} - {selectedSuiteState.suite.test_run_id} -
    -
    - Starttime:{' '} - {selectedSuiteState.suite.start_time - ? selectedSuiteState.suite.start_time.slice( - 0, - 16 - ) - : ''} -
    -
    -
    - i.id === parseInt(testId, 10) - )} - /> -
    - ) : ( - - )} -
    - ); -}; - -const SelectedTest = ({ test }) => { - const selectedTestStyles = css` - .flex-grow { - flex-grow: 1; - } - .flex-column { - display: flex; - flex-direction: column; - padding: 5px; - } - .list-header { - list-style: none; - padding-left: 0px; - } - .list-title { - color: grey; - } - background: #fff; - margin-top: 5px; - padding: 5px; - border-radius: 5px; - display: flex; - flex-direction: row; - table { - border-collapse: collapse; - table-layout: fixed; - width: 100%; - } - thead { - color: grey; - text-align: left; - border-bottom: 1px solid grey; - } - .table-item { - padding: 0.25rem 0rem; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - tr { - border-bottom: 1px solid #eee; - } - .tableLogLevel { - width: 10%; - } - .tableMessage { - width: 60%; - } - .tableTimeStamp { - width: 30%; - } - `; - - return test ? ( -
    -
    -
    -
    - - Name -
    -
      -
    • - - {test.name} -
    • -
    -
    -
    -
    - - Tags -
    -
      - {test.tags.map((tag, i) => { - return
    • {tag}
    • ; - })} -
    -
    -
    -
    - - Statuses -
    -
      -
    • Setup: {test.setup_status}
    • -
    • Execution: {test.execution_status}
    • -
    • Teardown: {test.teardown_status}
    • -
    -
    -
    -
    -
    -
    - - Log messages -
    -
    - - - - - - - - - {test.log_messages !== null && - test.log_messages.map( - ({ log_level, message, timestamp }, i) => { - return ( - - - - - - ); - } - )} - -
    Log levelMessageTimestamp
    -
    - {log_level} -
    -
    -
    - {message} -
    -
    -
    - {pickIcon('TIME')} - {timestamp} -
    -
    - - - - ) : null; -}; - -export default Suite; diff --git a/frontend/src/pages/Suite.jsx b/frontend/src/pages/Suite.jsx new file mode 100644 index 00000000..44187309 --- /dev/null +++ b/frontend/src/pages/Suite.jsx @@ -0,0 +1,113 @@ +// eslint-disable-next-line +import React from 'react'; +import { useEffect } from 'react'; +import { useParams } from 'react-router'; +import { useStateValue } from '../contexts/state'; +import theme from '../styles/theme'; +import BreadcrumbNav from '../components/BreadcrumbNav'; +import Notfound from '../components/NotFound'; +import ParentBuild from '../components/parentData/ParentBuild'; +import Loading from '../components/Loading'; +import Header from '../components/header/Header'; +import SuiteMetadata from '../components/metadata/SuiteMetadata'; +import TestList from '../components/suite/Testlist'; +import LogMessages from '../components/suite/LogMessages'; +import { ParentInfoContainer } from './Suite.styles'; + +const Suite = () => { + const { suiteId, buildId, seriesId, testId } = useParams(); + const [ + { + selectedSuiteState, + loadingState, + branchesState, + selectedBranchState, + }, + dispatch, + ] = useStateValue(); + + const branch_id = seriesId || selectedBranchState; + + useEffect(() => { + const fetchHistoryData = async () => { + if (branch_id && buildId) { + const branch = branchesState.series?.find( + ({ id: serie_id }) => serie_id === parseInt(branch_id, 10) + ); + dispatch({ + type: 'setSelectedBranch', + name: branch?.name, + id: seriesId, + team: branch?.team || ' ', + }); + dispatch({ type: 'setSelectedBuild', selectedBuild: buildId }); + } + }; + const fetchSuiteData = async () => { + dispatch({ type: 'setLoadingState', loadingState: true }); + try { + const res = await fetch( + `/data/series/${seriesId}/builds/${buildId}/suites/${suiteId}/?`, + {} + ); + const json = await res.json(); + dispatch({ type: 'setLoadingState', loadingState: false }); + dispatch({ type: 'setSelectedSuiteState', suite: json }); + } catch (error) { + //console.log(error); + } + }; + if (branchesState) { + fetchHistoryData(); + fetchSuiteData(); + } + + return () => { + dispatch({ type: 'flushSuiteState' }); + }; + }, [dispatch, branch_id, suiteId, buildId, seriesId, branchesState]); + + return ( +
    + {!selectedSuiteState || loadingState ? ( +
    + +
    + ) : selectedSuiteState.suite ? ( +
    +
    + Content loaded. +
    + +
    + + + + + + i.id === parseInt(testId, 10) + )} + /> +
    + ) : ( + + )} +
    + ); +}; + +export default Suite; diff --git a/frontend/src/pages/Suite.styles.js b/frontend/src/pages/Suite.styles.js new file mode 100644 index 00000000..b36e8ba6 --- /dev/null +++ b/frontend/src/pages/Suite.styles.js @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const ParentInfoContainer = styled.div` + display: flex; + padding: 20px 0; +`; diff --git a/frontend/src/pages/Team.js b/frontend/src/pages/Team.js index a876eb80..839e9d5c 100644 --- a/frontend/src/pages/Team.js +++ b/frontend/src/pages/Team.js @@ -4,15 +4,14 @@ import Card from '../components/Card'; import { useParams } from 'react-router'; import SelectedTeam from '../components/SelectedTeam'; import Loading from '../components/Loading'; -/** @jsx jsx */ -import { css, jsx } from '@emotion/core'; import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; -const Team = () => { - const TeamContainer = css` - padding-top: 20px; - `; +const TeamContainer = styled.div` + padding-top: 20px; +`; +const Team = () => { const [t] = useTranslation(['team']); const [{ loadingState, teamsState }, dispatch] = useStateValue(); @@ -45,7 +44,7 @@ const Team = () => { )} /> ) : ( -
    +

    {t('title')}

    {teamsState.map(({ name, series_count }, i) => { return ( @@ -56,7 +55,7 @@ const Team = () => { /> ); })} -
    + )}
    ); diff --git a/frontend/src/styles/baseComponents.js b/frontend/src/styles/baseComponents.js new file mode 100644 index 00000000..3f5e008e --- /dev/null +++ b/frontend/src/styles/baseComponents.js @@ -0,0 +1,52 @@ +import styled from 'styled-components'; + +const baseTable = styled.table` + table-layout: fixed; + width: 100%; + border-collapse: collapse; + padding: 5px 5px; + text-align: left; + vertical-align: middle; + word-wrap: break-word; + + thead { + background: var(--hermanni-grey); + } + + td { + } + td.test-result-undefined { + background: #eee; + } + + td, + th { + padding: 5px 5px; + text-align: center; + vertical-align: middle; + word-wrap: break-word; + } + + td { + background: #ffffff; + vertical-align: top; + } + + tr { + border-top: 1px solid #e5e5e5; + } + a { + text-decoration: none; + } +`; + +const overviewElement = styled.div` + padding: 10px; + border-style: solid; + border-width: thin; + display: block; + margin: 20px; + height: min-content; +`; + +export { baseTable, overviewElement }; diff --git a/frontend/src/styles/theme.js b/frontend/src/styles/theme.js index 4a0fae70..7cb68ae1 100644 --- a/frontend/src/styles/theme.js +++ b/frontend/src/styles/theme.js @@ -1,12 +1,12 @@ const theme = { spacing: { - xs: 20 + xs: 20, }, colors: { fail: '#F00', pass: '#008000', skipped: '#CCC', - siiliOrange: '#FF5200' + siiliOrange: '#FF5200', }, flexItem: { flexBasis: '30%', @@ -16,7 +16,7 @@ const theme = { minHeight: '20vh', backgroundColor: '#ffffff', cursor: 'pointer', - fontSize: '12px' + fontSize: '12px', }, baseTableStyle: ` fontSize: 12px; @@ -74,18 +74,18 @@ const theme = { linkColor: '#003399', activeLinkColor: '#001155', container: ` - background-color: #e9e8e8; - color: #222; - border: 1px solid #ccc; - p { line-height: 1.6 } + background-color: #e9e8e8; + color: #222; + border: 1px solid #ccc; + p { line-height: 1.6 } - nav { - border-right: 1px solid darkgrey; - background: #ddd; - padding: 0; - } - ` - } + nav { + border-right: 1px solid darkgrey; + background: #ddd; + padding: 0; + } + `, + }, }; export default theme; diff --git a/frontend/src/utils/colorTypes.js b/frontend/src/utils/colorTypes.js new file mode 100644 index 00000000..991a45d9 --- /dev/null +++ b/frontend/src/utils/colorTypes.js @@ -0,0 +1,16 @@ +export const colorTypes = { + 'gradient black': '#141312', + 'evidence grey': '#7B756F', + 'nero white': '#ffffff', + 'nelson purple': '#b8557b', + 'semolina red': '#C63757', + 'pirlo blue': '#067cc1', + 'titan green': '#238564', + 'kumpula yellow': '#FAF3E1', + 'toukola green': '#E9F5E1', + 'vallila blue': '#E1F1F7', + 'hermanni grey': '#EDECEC', + 'arabia red': '#faecec', + 'tonic grey': '#d7d4d3', + 'sparkling blue': '#80bfdb', +}; diff --git a/frontend/src/utils/graphTypes.js b/frontend/src/utils/graphTypes.js new file mode 100644 index 00000000..a15e2ed2 --- /dev/null +++ b/frontend/src/utils/graphTypes.js @@ -0,0 +1,3 @@ +export const suiteLabels = ['suites_failed', 'suites_passed', 'suites_skipped']; + +export const testLabels = ['tests_failed', 'tests_passed', 'tests_skipped']; diff --git a/frontend/src/utils/helpers.js b/frontend/src/utils/helpers.js index aff35412..e4b7a2dd 100644 --- a/frontend/src/utils/helpers.js +++ b/frontend/src/utils/helpers.js @@ -2,3 +2,8 @@ export const dashify = str => { const regex = /\s/g; return str.replace(regex, '-'); }; + +export const capitalCaseInitial = str => + str.charAt(0).toUpperCase() + str.slice(1); + +export const removeUnderscore = str => str.replace(/_/g, ' '); diff --git a/frontend/src/utils/i118n.js b/frontend/src/utils/i118n.js index 2a968bbc..c34237c1 100644 --- a/frontend/src/utils/i118n.js +++ b/frontend/src/utils/i118n.js @@ -3,13 +3,25 @@ import { initReactI18next } from 'react-i18next'; import frontpage from '../locales/en/frontpage.json'; import mainnav from '../locales/en/mainnav.json'; import team from '../locales/en/team.json'; +import parentData from '../locales/en/parentData.json'; +import overview from '../locales/en/overview.json'; +import header from '../locales/en/header.json'; +import history from '../locales/en/history.json'; +import buttons from '../locales/en/buttons.json'; +import metadata from '../locales/en/metadata.json'; const resources = { en: { frontpage, mainnav, - team - } + team, + parentData, + overview, + header, + history, + buttons, + metadata, + }, }; i18n.use(initReactI18next).init({ @@ -17,6 +29,6 @@ i18n.use(initReactI18next).init({ lng: 'en', debug: false, interpolation: { - escapeValue: false - } + escapeValue: false, + }, }); diff --git a/frontend/src/utils/parentDataTypes.js b/frontend/src/utils/parentDataTypes.js index a467a622..296b545e 100644 --- a/frontend/src/utils/parentDataTypes.js +++ b/frontend/src/utils/parentDataTypes.js @@ -1,16 +1,8 @@ -export const seriesTypes = ['team', 'name', 'builds']; -export const buildTypes = [ +export const buildTypes = [ 'team', 'name', 'build_number', 'build_id', 'status', - 'start_time' -]; -export const suiteTypes = [ - 'team', - 'name', - 'build_number', - 'build_id', - 'start_time' + 'start_time', ];