From 693f14505b014dc7e6b54121ec4cc55b7cc62ffb Mon Sep 17 00:00:00 2001 From: Ilank <63646693+ilan7empest@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:59:59 +0300 Subject: [PATCH 01/26] Impl [Tests] QA Sprint 171 done (#2847) --- package.json | 2 +- tests/features/MLFunction.feature | 27 +- tests/features/common-tools/common-consts.js | 3 + tests/features/common/actions/table.action.js | 15 +- .../page-objects/interactive-popup.po.js | 17 +- .../common/page-objects/jobs-monitoring.po.js | 38 +- .../common/page-objects/ml-functions.po.js | 1 + .../page-objects/project-settings.po.js | 9 + .../common/page-objects/projects.po.js | 18 + tests/features/datasets.feature | 1 + tests/features/jobsAndWorkflows.feature | 4 + tests/features/jobsMonitoring.feature | 419 ++++++++++++++++-- tests/features/models.feature | 1 + tests/features/projectMonitoring.feature | 8 +- tests/features/projectSettings.feature | 45 +- tests/features/projectsPage.feature | 14 +- .../features/step-definitions/table.steps.js | 14 +- tests/mockServer/data/funcs.json | 92 +++- tests/mockServer/data/run.json | 47 +- tests/mockServer/data/runs.json | 47 +- tests/mockServer/dateSynchronization.js | 2 +- tests/mockServer/mock.js | 55 ++- 22 files changed, 799 insertions(+), 80 deletions(-) diff --git a/package.json b/package.json index fffced7c7..b2d4eb6f4 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "body-parser": "^1.19.0", "case-sensitive-paths-webpack-plugin": "^2.4.0", "chai": "^4.3.4", - "chromedriver": "^128.0.0", + "chromedriver": "^130.0.0", "css-loader": "^6.5.1", "cucumber-html-reporter": "^5.3.0", "eslint": "^8.57.0", diff --git a/tests/features/MLFunction.feature b/tests/features/MLFunction.feature index a7a2c1e4f..f87e862a6 100644 --- a/tests/features/MLFunction.feature +++ b/tests/features/MLFunction.feature @@ -699,7 +699,7 @@ Feature: ML Functions When collapse "Resources_Accordion" on "New_Function" wizard Then "Deploy_Button" element on "New_Function" should contains "Create" value Then click on "Deploy_Button" element on "New_Function" wizard - Then click on "Cross_Close_Button" element on "ML_Function_Info_Pane" wizard + And wait load page Then check "new-aqa-function-01" value in "name" column in "Functions_Table" table on "ML_Functions" wizard @MLF @@ -1321,6 +1321,8 @@ Feature: ML Functions When click on cell with row index 1 in "name" column in "Functions_Table" table on "ML_Functions" wizard And wait load page Then verify redirection from "projects/default/functions/INVALID/latest/overview" to "projects/default/functions" + And wait load page + And wait load page Then verify "Notification_Pop_Up" element visibility on "Notification_Popup" wizard And wait load page Then "Notification_Pop_Up" element on "Notification_Popup" should contains "This function either does not exist or was deleted" value @@ -1334,16 +1336,16 @@ Feature: ML Functions Then verify "Date_Picker_Filter_Dropdown" element visibility on "ML_Functions" wizard When select "Any time" option in "Date_Picker_Filter_Dropdown" filter dropdown on "ML_Functions" wizard And wait load page - When click on cell with row index 1 in "name" column in "Functions_Table" table on "ML_Functions" wizard + When click on cell with row index 2 in "name" column in "Functions_Table" table on "ML_Functions" wizard And wait load page - Then verify redirection from "projects/default/functions/85957751e571a92e07213781f5e0c35bfbe42c64/INVALID" to "projects/default/functions/85957751e571a92e07213781f5e0c35bfbe42c64/overview" + Then verify redirection from "projects/default/functions/model-monitoring-stream/latest/INVALID" to "projects/default/functions/model-monitoring-stream/latest/overview" Then select "Code" tab in "Info_Pane_Tab_Selector" on "ML_Function_Info_Pane" wizard And wait load page - Then verify redirection from "projects/default/functions/85957751e571a92e07213781f5e0c35bfbe42c64/INVALID" to "projects/default/functions/85957751e571a92e07213781f5e0c35bfbe42c64/overview" + Then verify redirection from "projects/default/functions/model-monitoring-stream/latest/INVALID" to "projects/default/functions/model-monitoring-stream/latest/overview" Then select "Build Log" tab in "Info_Pane_Tab_Selector" on "ML_Function_Info_Pane" wizard And wait load page - Then verify redirection from "projects/default/functions/85957751e571a92e07213781f5e0c35bfbe42c64/INVALID" to "projects/default/functions/85957751e571a92e07213781f5e0c35bfbe42c64/overview" - Then verify redirection from "projects/default/INVALID/85957751e571a92e07213781f5e0c35bfbe42c64/overview" to "projects" + Then verify redirection from "projects/default/functions/model-monitoring-stream/latest/INVALID" to "projects/default/functions/model-monitoring-stream/latest/overview" + Then verify redirection from "projects/default/INVALID/model-monitoring-stream/latest/overview" to "projects" @MLF @smoke @@ -1412,8 +1414,19 @@ Feature: ML Functions Then verify "New_Function_Build_Commands_Text_Area" not input element in "Code_Accordion" on "New_Function" wizard is enabled Then click on "Save_Button" element on "New_Function" wizard And wait load page - Then "Header" element on "ML_Function_Info_Pane" should contains "demo-function-02" value + Then check "demo-function-02" value in "name" column in "Functions_Table" table on "ML_Functions" wizard + Then verify "Table_FilterBy_Button" element visibility on "ML_Functions" wizard + Then click on "Table_FilterBy_Button" element on "ML_Functions" wizard + Then "Title" element on "FilterBy_Popup" should contains "Filter by" value + Then verify "Show_Untagged" element visibility on "FilterBy_Popup" wizard + Then check "Show_Untagged" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And wait load page + Then check "demo-function-02" value in "name" column in "Functions_Table" table on "ML_Functions" wizard + When click on cell with value "demo-function-02" in "name" column in "Functions_Table" table on "ML_Functions" wizard Then check "demo-function-02" value in "name" column in "Overview_Table" table on "ML_Function_Info_Pane" wizard + Then "Header" element on "ML_Function_Info_Pane" should contains "demo-function-02" value @MLF @passive diff --git a/tests/features/common-tools/common-consts.js b/tests/features/common-tools/common-consts.js index 76167584a..cb2920e34 100644 --- a/tests/features/common-tools/common-consts.js +++ b/tests/features/common-tools/common-consts.js @@ -495,6 +495,7 @@ module.exports = { 'Run on spot:', 'Node selector:', 'Priority:', + 'Handler:', 'Parameters:', 'Function:', 'Function tag:', @@ -598,6 +599,8 @@ module.exports = { }, No_Data_Message: { Common_Message_Jobs_Monitoring: /No data matches the filter: "Start time: \d{2}\/\d{2}\/\d{4} \d{2}:\d{2} - \d{2}\/\d{2}\/\d{4} \d{2}:\d{2}, Project: test"/, + Common_Message_Jobs_Monitoring_Status: /No data matches the filter: "Created at: \d{2}\/\d{2}\/\d{4} \d{2}:\d{2} - \d{2}\/\d{2}\/\d{4} \d{2}:\d{2}, Status: (.+?)"/, + Common_Message_Jobs_Monitoring_Type: /No data matches the filter: "Start time: \d{2}\/\d{2}\/\d{4} \d{2}:\d{2} - \d{2}\/\d{2}\/\d{4} \d{2}:\d{2}, Type: (.+?)"/, Common_Message_Jobs_Monitoring_Scheduled: /No data matches the filter: "Scheduled at: \d{2}\/\d{2}\/\d{4} \d{2}:\d{2} - \d{2}\/\d{2}\/\d{4} \d{2}:\d{2}, Project: test"/, Common_Message: 'No data matches the filter: "Version Tag: latest, Name: ccccc"', Common_Message_Feature: 'No data matches the filter: "Version Tag: latest"', diff --git a/tests/features/common/actions/table.action.js b/tests/features/common/actions/table.action.js index a2d714693..e4097aaea 100644 --- a/tests/features/common/actions/table.action.js +++ b/tests/features/common/actions/table.action.js @@ -81,7 +81,7 @@ const action = { `Value "${subString}" does not includes in all values: [${arr}]` ) }, - isContainsSubstringInColumnAttributrCells: async function( + isContainsSubstringInColumnAttributeCells: async function( driver, table, columnName, @@ -94,6 +94,19 @@ const action = { `Value "${value}" does not includes in all values: [${arr}]` ) }, + isContainsSubstringInColumnAttributeListCells: async function( + driver, + table, + columnName, + value + ) { + const arr = await getColumnValuesAttribute(driver, table, columnName) + expect(arr.length > 0).to.equal(true) + expect(arr.every(item => value.includes(item))).to.equal( + true, + `Value "${value}" does not includes in all values: [${arr}]` + ) + }, isContainsSubstringInColumnDropdownCells: async function( driver, table, diff --git a/tests/features/common/page-objects/interactive-popup.po.js b/tests/features/common/page-objects/interactive-popup.po.js index 9a1152566..dbe3e4095 100644 --- a/tests/features/common/page-objects/interactive-popup.po.js +++ b/tests/features/common/page-objects/interactive-popup.po.js @@ -1512,7 +1512,14 @@ module.exports = { }, filterByPopup: { Title: By.css('[data-testid="pop-up-dialog"] h3'), - Table_Label_Filter_Input: commonLabelFilterInput, + Table_Label_Filter_Input: inputGroup( + generateInputGroup( + '[data-testid="labels-form-field-input"]', + true, + false, + 'svg' + ) + ), Table_Project_Filter_Input: commonProjectFilterInput, Table_Tree_Filter_Dropdown: commonTableTreeFilterDropdown, Status_Filter_Element: By.css('[data-testid="state-form-field-select"]'), @@ -1538,6 +1545,14 @@ module.exports = { icon: '' } }), + Show_Untagged: checkboxComponent({ + root: '#overlay_container .form-field-checkbox input', + elements: { + checkbox: '', + name: '', + icon: '' + } + }), Status_All_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(1)', elements: { diff --git a/tests/features/common/page-objects/jobs-monitoring.po.js b/tests/features/common/page-objects/jobs-monitoring.po.js index e8bcaaa09..9a64b7faf 100644 --- a/tests/features/common/page-objects/jobs-monitoring.po.js +++ b/tests/features/common/page-objects/jobs-monitoring.po.js @@ -20,13 +20,16 @@ such restriction. import { By } from 'selenium-webdriver' import commonTable from '../components/table.component' import dropdownComponent from '../components/dropdown.component' +import labelComponent from '../components/label.component' import { generateDropdownGroup, - generateInputGroup + generateInputGroup, + generateLabelGroup } from '../../common-tools/common-tools' import inputGroup from '../components/input-group.component' import checkboxComponent from '../components/checkbox.component' import datepicker from '../components/date-picker.component' +import actionMenu from '../components/action-menu.component' const tabSelector = { root: '.content .content-menu', @@ -42,6 +45,14 @@ const tabSelector = { } } +const actionMenuStructure = { + root: '.actions-menu__container', + menuElements: { + open_button: 'button', + options: '.actions-menu__body .actions-menu__option' + } +} + const overallTable = { root: '.table__content', header: { @@ -68,7 +79,30 @@ const overallTable = { uid: '.table-body__cell:nth-of-type(1) a .date-uid-row .link-subtext:nth-of-type(2)', duration: '.table-body__cell:nth-of-type(3) .data-ellipsis', - owner: '.table-body__cell:nth-of-type(4) .data-ellipsis' + owner: '.table-body__cell:nth-of-type(4) .data-ellipsis', + action_menu: { + componentType: actionMenu, + structure: actionMenuStructure + }, + labels: { + componentType: dropdownComponent, + structure: generateDropdownGroup( + '.table-body__cell:nth-of-type(7)', + '.chip-block span.chips_button', + '.chip-block-hidden_visible .data-ellipsis.tooltip-wrapper', + false, + false + ) + }, + type: { + componentType: labelComponent, + structure: generateLabelGroup( + '.table-body__cell:nth-of-type(3)', + '.data-ellipsis', + true, + '.tooltip .tooltip__text span' + ) + }, } } } diff --git a/tests/features/common/page-objects/ml-functions.po.js b/tests/features/common/page-objects/ml-functions.po.js index 702526f74..e090495ae 100644 --- a/tests/features/common/page-objects/ml-functions.po.js +++ b/tests/features/common/page-objects/ml-functions.po.js @@ -94,6 +94,7 @@ module.exports = { false ) ), + Table_FilterBy_Button: By.css('[data-testid="filter-menu-btn-tooltip-wrapper"]'), New_Function_Button: By.css('.content [data-testid="btn"]'), Table_Refresh_Button: By.css( '.content [data-testid="refresh-tooltip-wrapper"]' diff --git a/tests/features/common/page-objects/project-settings.po.js b/tests/features/common/page-objects/project-settings.po.js index 05e126669..a39df6565 100644 --- a/tests/features/common/page-objects/project-settings.po.js +++ b/tests/features/common/page-objects/project-settings.po.js @@ -22,6 +22,7 @@ import commonTable from '../components/table.component' import inputGroup from '../components/input-group.component' import textAreaGroup from '../components/text-area.component' import { generateInputGroup, generateTextAreaGroup } from '../../common-tools/common-tools' +import checkboxComponent from '../components/checkbox.component' const tabSelector = { @@ -185,6 +186,14 @@ module.exports = { '.input-label-value' ) ), + Pull_At_Runtime_Checkbox: checkboxComponent({ + root: '.settings__source [data-testid="form-field-checkbox"]', + elements: { + checkbox: 'input', + name: '', + icon: '' + } + }) }, secretsTab: { Secrets_Table: commonTable(secretsTable), diff --git a/tests/features/common/page-objects/projects.po.js b/tests/features/common/page-objects/projects.po.js index 6964970c5..18172bf2f 100644 --- a/tests/features/common/page-objects/projects.po.js +++ b/tests/features/common/page-objects/projects.po.js @@ -58,6 +58,24 @@ const ProjectsTableSelector = { '.tooltip .tooltip__text span' ) }, + labels_key: { + componentType: labelComponent, + structure: generateLabelGroup( + '[data-testid="project-card__labels"] .chip-block .input-label-key', + false, + false, + '.tooltip .tooltip__text span' + ) + }, + labels_value: { + componentType: labelComponent, + structure: generateLabelGroup( + '[data-testid="project-card__labels"] .chip-block .input-label-value', + false, + false, + '.tooltip .tooltip__text span' + ) + }, labels: { componentType: dropdownComponent, structure: generateDropdownGroup( diff --git a/tests/features/datasets.feature b/tests/features/datasets.feature index 46bafc152..5aff63985 100644 --- a/tests/features/datasets.feature +++ b/tests/features/datasets.feature @@ -172,6 +172,7 @@ Feature: Datasets Page Then verify "Header" element visibility on "Datasets_Info_Pane" wizard Then "Header" element on "Datasets_Info_Pane" should contains "test-dataset" value Then refresh a page + And wait load page Then verify "Header" element visibility on "Datasets_Info_Pane" wizard Then "Header" element on "Datasets_Info_Pane" should contains "test-dataset" value diff --git a/tests/features/jobsAndWorkflows.feature b/tests/features/jobsAndWorkflows.feature index 5cf5ffa87..a78245d86 100644 --- a/tests/features/jobsAndWorkflows.feature +++ b/tests/features/jobsAndWorkflows.feature @@ -1241,9 +1241,13 @@ Feature: Jobs and workflows Then select "Pods" tab in "Info_Pane_Tab_Selector" on "Workflows_Monitor_Tab_Info_Pane" wizard And wait load page Then verify redirection from "projects/churn-project-admin/jobs/monitor-workflows/workflow/eaae138e-439a-47fa-93c6-ba0fe1dc3b79/07f98fb46a424b2dbee5247b35f37727/INVALID" to "projects/churn-project-admin/jobs/monitor-workflows/workflow/eaae138e-439a-47fa-93c6-ba0fe1dc3b79/07f98fb46a424b2dbee5247b35f37727/overview" + And wait load page Then verify redirection from "projects/churn-project-admin/jobs/monitor-workflows/workflow/INVALID/07f98fb46a424b2dbee5247b35f37727/overview" to "projects/churn-project-admin/jobs/monitor-workflows" + And wait load page Then verify redirection from "projects/churn-project-admin/jobs/monitor-workflows/workflow/eaae138e-439a-47fa-93c6-ba0fe1dc3b79/INVALID/overview" to "projects/churn-project-admin/jobs/monitor-workflows" + And wait load page Then verify redirection from "projects/INVALID/jobs/monitor-workflows/workflow/eaae138e-439a-47fa-93c6-ba0fe1dc3b79/07f98fb46a424b2dbee5247b35f37727/overview" to "projects" + And wait load page @MLJW @smoke diff --git a/tests/features/jobsMonitoring.feature b/tests/features/jobsMonitoring.feature index 1305b2863..18bf87dc2 100644 --- a/tests/features/jobsMonitoring.feature +++ b/tests/features/jobsMonitoring.feature @@ -8,7 +8,6 @@ Feature: Jobs Monitoring Page Given open url And wait load page Then verify "Monitoring_Container" element visibility in "Projects_Monitoring_Container" on "Projects" wizard - Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "19" value When click on "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" wizard And wait load page Then verify redirection to "projects/*/jobs-monitoring/jobs" @@ -17,7 +16,6 @@ Feature: Jobs Monitoring Page Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 6 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" @@ -52,7 +50,6 @@ Feature: Jobs Monitoring Page Then navigate back And wait load page Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "16" value When click on "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard And wait load page Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard @@ -67,11 +64,9 @@ Feature: Jobs Monitoring Page Then "Status_Jobs_Running_Checkbox" element should be checked on "FilterBy_Popup" wizard Then "Status_Pending_Checkbox" element should be checked on "FilterBy_Popup" wizard Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 15 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard And wait load page Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "2" value When click on "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard And wait load page Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard @@ -88,11 +83,9 @@ Feature: Jobs Monitoring Page Then "Status_Aborted_Checkbox" element should be checked on "FilterBy_Popup" wizard Then "Status_Jobs_Error_Checkbox" element should be checked on "FilterBy_Popup" wizard Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 2 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard And wait load page Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "1" value When click on "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard And wait load page Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard @@ -110,7 +103,6 @@ Feature: Jobs Monitoring Page Then "Status_Jobs_Error_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then "Status_Jobs_Completed_Checkbox" element should be checked on "FilterBy_Popup" wizard Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 1 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard And select "crossTab" with "Jobs monitoring" value in breadcrumbs menu Then verify redirection to "projects/*/jobs-monitoring/jobs" Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard @@ -124,7 +116,7 @@ Feature: Jobs Monitoring Page Then click on "Apply_Button" element on "FilterBy_Popup" wizard And wait load page Then "Error_Message" component on "Jobs_Monitoring_Jobs_Tab" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring" - + @MLJM @smoke Scenario: MLJM004 - Check search by name, project name, filter by Date picker on Jobs tab of Jobs monitoring page @@ -155,7 +147,7 @@ Feature: Jobs Monitoring Page And wait load page Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Jobs_Tab" wizard selected option value "Past 24 hours" Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 6 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 7 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard When select "Any time" option in "Date_Picker_Filter_Dropdown" filter dropdown on "Jobs_Monitoring_Jobs_Tab" wizard And wait load page Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Jobs_Tab" wizard selected option value "Any time" @@ -169,21 +161,227 @@ Feature: Jobs Monitoring Page And wait load page Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Jobs_Tab" wizard selected option value "Past week" Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 6 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 7 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard When select "Past month" option in "Date_Picker_Filter_Dropdown" filter dropdown on "Jobs_Monitoring_Jobs_Tab" wizard And wait load page Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Jobs_Tab" wizard selected option value "Past month" Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 6 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 7 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard When select "Past year" option in "Date_Picker_Filter_Dropdown" filter dropdown on "Jobs_Monitoring_Jobs_Tab" wizard And wait load page Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Jobs_Tab" wizard selected option value "Past year" Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard - Then verify that 6 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 7 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard When pick up "Custom range" from "09/03/2024 00:00" to "09/04/2024 00:00" in "Date_Time_Picker" via "Date_Picker_Filter_Dropdown" on "Jobs_Monitoring_Jobs_Tab" wizard Then verify from "09/03/2024 00:00" to "09/04/2024 00:00" filter band in "Custom_Range_Filter_Dropdown" filter dropdown on "Jobs_Monitoring_Jobs_Tab" wizard And wait load page - + + @MLJM + @smoke + Scenario: MLJM007 - Check filter by Statuses and View Yaml action on Jobs tab of Jobs monitoring page + Given open url + And wait load page + When click on "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/jobs" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + When select "Aborted" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Aborted" + When select "Aborting" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Aborted, Aborting" + When select "Completed" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "3 items selected" + When select "Error" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "4 items selected" + When select "Running" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "5 items selected" + When select "Pending" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then select "View YAML" option in action menu on "Jobs_Monitoring_Jobs_Tab" wizard in "Jobs_Table" table at row with "test" value in "name" column + Then verify if "View_YAML" popup dialog appears + Then verify "Cross_Cancel_Button" element visibility on "View_YAML" wizard + Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard + + @MLJM + @smoke + Scenario: MLJM010 - Check filter by Types on Jobs tab of Jobs monitoring page + Given open url + And wait load page + When click on "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/jobs" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + When select "Job" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Job" + When select "Nuclio" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Nuclio" + When select "Application" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Type" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Application" + When select "Serving" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Type" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Serving" + When select "Spark" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Type" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Spark" + When select "Horovod" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Horovod" + When select "Dask" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Type" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Dask" + When select "Databricks" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Type" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Databricks" + When select "Local" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Type" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Local" + When select "Handler" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Handler" + + @MLJM + @smoke + Scenario: MLJM011 - Check filter by Labels on Jobs tab of Jobs monitoring page + Given open url + And wait load page + When click on "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/jobs" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then verify "Table_Label_Filter_Input" element visibility on "FilterBy_Popup" wizard + Then type value "host" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + And wait load page + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then value in "labels" column with "dropdowns" in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard should contains "host" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then type value "author=yaronh" to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + And wait load page + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then value in "labels" column with "dropdowns" in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard should contains "author=yaronh" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Jobs_Tab" wizard + Then type value " " to "Table_Label_Filter_Input" field on "FilterBy_Popup" wizard + Then verify "Table_Label_Filter_Input" on "FilterBy_Popup" wizard should display hover warning "Input_Hint"."Input_Field_Invalid" + And select "Scheduled" tab in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard + And wait load page + And select "Jobs" tab in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard + And wait load page + Then verify options in action menu on "Jobs_Monitoring_Jobs_Tab" wizard in "Jobs_Table" table with "Job" value in "type" column should contains "Jobs_And_Workflows"."Job_Action_Menu_Options" + Then select "Batch re-run" option in action menu on "Jobs_Monitoring_Jobs_Tab" wizard in "Jobs_Table" table at row with "sef" value in "name" column + And wait load page + Then verify "Title" element visibility on "Modal_Wizard_Form" wizard + And wait load page + Then "Title" element on "Modal_Wizard_Form" should contains "Batch Re-Run" value + Then verify "Run_Button" element visibility on "Modal_Wizard_Form" wizard + Then "Run_Button" element on "Modal_Wizard_Form" should contains "Run" value + And click on "Run_Button" element on "Modal_Wizard_Form" wizard + Then wait for 5 seconds + Then verify "Notification_Pop_Up" element visibility on "Notification_Popup" wizard + Then "Notification_Pop_Up" element on "Notification_Popup" should contains "The batch run was started" value + And wait load page + Then verify "Notification_Pop_Up_Cross_Close_Button" element visibility on "Notification_Popup" wizard + Then click on "Notification_Pop_Up_Cross_Close_Button" element on "Notification_Popup" wizard + And wait load page + Then select "Delete" option in action menu on "Jobs_Monitoring_Jobs_Tab" wizard in "Jobs_Table" table at row with "qwe" value in "name" column + And wait load page + Then verify if "Confirm_Popup" popup dialog appears + Then "Title" element on "Confirm_Popup" should contains "Delete job?" value + When click on "Delete_Button" element on "Confirm_Popup" wizard + Then wait for 5 seconds + Then verify "Notification_Pop_Up" element visibility on "Notification_Popup" wizard + Then "Notification_Pop_Up" element on "Notification_Popup" should contains "Job is successfully deleted" value + And wait load page + Then verify "Notification_Pop_Up_Cross_Close_Button" element visibility on "Notification_Popup" wizard + Then click on "Notification_Pop_Up_Cross_Close_Button" element on "Notification_Popup" wizard + And wait load page + @MLJM @smoke Scenario: MLJM002 - Check components on Workflows tab of Jobs monitoring page @@ -192,7 +390,6 @@ Feature: Jobs Monitoring Page Then verify "Monitoring_Container" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Monitoring_Workflows_Box" element visibility in "Projects_Monitoring_Container" on "Projects" wizard Then verify "Total_Counter_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "2" value When click on "See_All_Link" element in "Monitoring_Workflows_Box" on "Projects" wizard And wait load page Then verify redirection to "projects/*/jobs-monitoring/workflows" @@ -201,7 +398,6 @@ Feature: Jobs Monitoring Page Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard - Then verify that 2 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Workflows_Tab" wizard selected option value "Past 24 hours" @@ -228,7 +424,6 @@ Feature: Jobs Monitoring Page Then navigate back And wait load page Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value When click on "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard And wait load page Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard @@ -241,11 +436,9 @@ Feature: Jobs Monitoring Page Then "Status_Workflows_Error_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then "Status_Failed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then "Status_Workflows_Completed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard - Then verify that 0 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard And wait load page Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value When click on "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard And wait load page Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard @@ -259,11 +452,9 @@ Feature: Jobs Monitoring Page Then "Status_Workflows_Running_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then "Status_Workflows_Completed_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard - Then verify that 1 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard And wait load page Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value When click on "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard And wait load page Then verify "Workflows" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Workflows_Tab" wizard @@ -277,7 +468,6 @@ Feature: Jobs Monitoring Page Then "Status_Workflows_Running_Checkbox" element should be unchecked on "FilterBy_Popup" wizard Then "Status_Workflows_Completed_Checkbox" element should be checked on "FilterBy_Popup" wizard Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard - Then verify that 1 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard And select "crossTab" with "Jobs monitoring" value in breadcrumbs menu Then verify redirection to "projects/*/jobs-monitoring/jobs" Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard @@ -361,6 +551,51 @@ Feature: Jobs Monitoring Page When pick up "Custom range" from "09/03/2024 00:00" to "09/04/2024 00:00" in "Date_Time_Picker" via "Date_Picker_Filter_Dropdown" on "Jobs_Monitoring_Workflows_Tab" wizard Then verify from "09/03/2024 00:00" to "09/04/2024 00:00" filter band in "Custom_Range_Filter_Dropdown" filter dropdown on "Jobs_Monitoring_Workflows_Tab" wizard And wait load page + + @MLJM + @smoke + Scenario: MLJM008 - Check filter by Statuses and View Yaml action on Workflows tab of Jobs monitoring page + Given open url + And wait load page + When click on "See_All_Link" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/workflows" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + When select "Error" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + And verify "No_Data_Message" element visibility on "commonPagesHeader" wizard + Then "No_Data_Message" component on "commonPagesHeader" should be equal "No_Data_Message"."Common_Message_Jobs_Monitoring_Status" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Error" + When select "Failed" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Error, Failed" + When select "Running" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "3 items selected" + When select "Completed" option in "Status_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify "Status_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Workflows_Tab" wizard + Then select "View YAML" option in action menu on "Jobs_Monitoring_Workflows_Tab" wizard in "Workflows_Table" table at row with "kfpipeline 2021-07-06 11-16-28" value in "name" column + Then verify if "View_YAML" popup dialog appears + Then verify "Cross_Cancel_Button" element visibility on "View_YAML" wizard + Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard @MLJM @smoke @@ -372,7 +607,6 @@ Feature: Jobs Monitoring Page Then verify "Total_Job_Counter_Title" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard Then "Total_Job_Counter_Title" element in "Monitoring_Scheduled_Box" on "Projects" should contains "Jobs" value Then verify "Total_Job_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard - Then "Total_Job_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "7" value Then verify "Jobs_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard When click on "Jobs_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard And wait load page @@ -382,7 +616,6 @@ Feature: Jobs Monitoring Page Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" Then verify "Scheduled" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard - Then verify that 7 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Scheduled_Tab" wizard selected option value "Next 24 hours" @@ -408,7 +641,6 @@ Feature: Jobs Monitoring Page Then verify "Total_Workflows_Counter_Title" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard Then "Total_Workflows_Counter_Title" element in "Monitoring_Scheduled_Box" on "Projects" should contains "Workflows" value Then verify "Total_Workflows_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard - Then "Total_Workflows_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "1" value Then verify "Workflows_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard When click on "Workflows_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard Then verify redirection to "projects/*/jobs-monitoring/scheduled" @@ -417,7 +649,6 @@ Feature: Jobs Monitoring Page Then verify "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard should contains "Jobs_Monitoring"."Tab_List" Then verify "Scheduled" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard - Then verify that 1 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Search_By_Name_Filter_Input" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify "Date_Picker_Filter_Dropdown" dropdown on "Jobs_Monitoring_Scheduled_Tab" wizard selected option value "Next 24 hours" @@ -444,7 +675,6 @@ Feature: Jobs Monitoring Page Then verify "Total_Scheduled_Title" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard Then "Total_Scheduled_Title" element in "Monitoring_Scheduled_Box" on "Projects" should contains "Total" value Then verify "Total_Job_Counter_Number" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard - Then "Total_Scheduled_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "8" value Then verify "Total_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard When click on "Total_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard And wait load page @@ -521,3 +751,138 @@ Feature: Jobs Monitoring Page When pick up "Custom range" from "09/03/2024 00:00" to "09/04/2024 00:00" in "Date_Time_Picker" via "Date_Picker_Filter_Dropdown" on "Jobs_Monitoring_Scheduled_Tab" wizard Then verify from "09/03/2024 00:00" to "09/04/2024 00:00" filter band in "Custom_Range_Filter_Dropdown" filter dropdown on "Jobs_Monitoring_Scheduled_Tab" wizard And wait load page + + @MLJM + @smoke + Scenario: MLJM009 - Check filter by Statuses and View Yaml action on Scheduled tab of Jobs monitoring page + Given open url + And wait load page + When click on "Total_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/scheduled" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "All" + When select "Jobs" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then select "View YAML" option in action menu on "Jobs_Monitoring_Scheduled_Tab" wizard in "Scheduled_Table" table at row with "clean-data" value in "name" column + Then verify if "View_YAML" popup dialog appears + Then verify "Cross_Cancel_Button" element visibility on "View_YAML" wizard + Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard + Then click on "Cross_Cancel_Button" element on "View_YAML" wizard + And wait load page + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Jobs" + When select "Workflows" option in "Type_Filter_Dropdown" filter dropdown on "FilterBy_Popup" wizard + Then click on "Title" element on "FilterBy_Popup" wizard + Then click on "Apply_Button" element on "FilterBy_Popup" wizard + And wait load page + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify "Type_Filter_Dropdown" dropdown on "FilterBy_Popup" wizard selected option value "Workflows" + Then click on "Table_FilterBy_Button" element on "Jobs_Monitoring_Scheduled_Tab" wizard + Then select "View YAML" option in action menu on "Jobs_Monitoring_Scheduled_Tab" wizard in "Scheduled_Table" table at row with "main3" value in "name" column + Then verify if "View_YAML" popup dialog appears + Then verify "Cross_Cancel_Button" element visibility on "View_YAML" wizard + Then verify "YAML_Modal_Container" element visibility on "View_YAML" wizard + + @MLJM + @smoke + Scenario: MLJM012 - Check jobs/workflows/scheduled count consistency among counter data and Jobs monitoring Tabs + Given open url + And wait load page + Then verify "Monitoring_Container" element visibility in "Projects_Monitoring_Container" on "Projects" wizard + Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "20" value + When click on "See_All_Link" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/jobs" + Then verify "Jobs" tab is active in "Cross_Jobs_Tab_Selector" on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 7 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then navigate back + And wait load page + Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard + Then "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "16" value + When click on "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 15 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard + Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "3" value + When click on "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 3 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard + Then "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "1" value + When click on "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" wizard + And wait load page + Then verify "Jobs_Table" element visibility on "Jobs_Monitoring_Jobs_Tab" wizard + Then verify that 1 row elements are displayed in "Jobs_Table" on "Jobs_Monitoring_Jobs_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Total_Counter_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "3" value + When click on "See_All_Link" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/workflows" + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify that 3 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then navigate back + And wait load page + Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value + When click on "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify that 0 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value + When click on "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify that 1 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard + Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "2" value + When click on "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard + And wait load page + Then verify "Workflows_Table" element visibility on "Jobs_Monitoring_Workflows_Tab" wizard + Then verify that 2 row elements are displayed in "Workflows_Table" on "Jobs_Monitoring_Workflows_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then "Total_Job_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "7" value + Then verify "Jobs_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + When click on "Jobs_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/scheduled" + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify that 7 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard + Then navigate back + And wait load page + Then "Total_Workflows_Counter_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "1" value + Then verify "Workflows_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + When click on "Workflows_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/scheduled" + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify that 1 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard + Then click on breadcrumbs "projectsPage" label on "commonPagesHeader" wizard + And wait load page + Then verify redirection to "projects" + Then "Total_Scheduled_Number" element in "Monitoring_Scheduled_Box" on "Projects" should contains "8" value + Then verify "Total_See_All_Link" element visibility in "Monitoring_Scheduled_Box" on "Projects" wizard + When click on "Total_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard + And wait load page + Then verify redirection to "projects/*/jobs-monitoring/scheduled" + Then verify "Scheduled_Table" element visibility on "Jobs_Monitoring_Scheduled_Tab" wizard + Then verify that 8 row elements are displayed in "Scheduled_Table" on "Jobs_Monitoring_Scheduled_Tab" wizard + diff --git a/tests/features/models.feature b/tests/features/models.feature index e77b17095..f31cf4278 100644 --- a/tests/features/models.feature +++ b/tests/features/models.feature @@ -708,6 +708,7 @@ Feature: Models Page Then verify "Header" element visibility on "Models_Info_Pane" wizard Then "Header" element on "Models_Info_Pane" should contains "test-model" value Then refresh a page + And wait load page Then verify "Header" element visibility on "Models_Info_Pane" wizard Then "Header" element on "Models_Info_Pane" should contains "test-model" value diff --git a/tests/features/projectMonitoring.feature b/tests/features/projectMonitoring.feature index c664b5272..57214dcf1 100644 --- a/tests/features/projectMonitoring.feature +++ b/tests/features/projectMonitoring.feature @@ -23,7 +23,7 @@ Feature: Project Monitoring Page @MLPM @passive @smoke - Scenario: MLNB001 - Check all mandatory components on Navigation Bar + Scenario: MLPM001 - Check all mandatory components on Navigation Bar Given open url And click on row root with value "default" in "name" column in "Projects_Table" table on "Projects" wizard And wait load page @@ -180,12 +180,12 @@ Feature: Project Monitoring Page Then verify "New_File_Type_Dropdown" dropdown on "Register_File_Popup" wizard selected option value "Table" @MLPM - @FAILED_TODO - #TODO: 'Register Model' option is missing in list of 'Create New' dropdown in demo mode @passive @smoke Scenario: MLPM005 - Check all mandatory components on Register Model Popup Given open url + And turn on demo mode + And wait load page And click on row root with value "default" in "name" column in "Projects_Table" table on "Projects" wizard And wait load page Then verify "Create_New" element visibility on "Project" wizard @@ -633,7 +633,7 @@ Feature: Project Monitoring Page @MLPM @passive @smoke - Scenario: MLB003 - Verify behaviour of Breadcrumbs menu + Scenario: MLPM022 - Verify behaviour of Breadcrumbs menu Given open url And click on row root with value "churn-project-admin" in "name" column in "Projects_Table" table on "Projects" wizard And wait load page diff --git a/tests/features/projectSettings.feature b/tests/features/projectSettings.feature index eaf788842..ec349ad03 100644 --- a/tests/features/projectSettings.feature +++ b/tests/features/projectSettings.feature @@ -25,6 +25,34 @@ Feature: Project Settings page Then verify "Source_URL_Input" on "Project_Settings_General_Tab" wizard should display "Input_Hint"."Source_URL_Input" Then type value " " to "Source_URL_Input" field on "Project_Settings_General_Tab" wizard Then verify "Source_URL_Input" on "Project_Settings_General_Tab" wizard should display hover warning "Input_Hint"."Input_Field_Invalid" + Then type value "test" to "Source_URL_Input" field on "Project_Settings_General_Tab" wizard + Then verify "Pull_At_Runtime_Checkbox" element visibility on "Project_Settings_General_Tab" wizard + Then "Pull_At_Runtime_Checkbox" element should be unchecked on "Project_Settings_General_Tab" wizard + Then check "Pull_At_Runtime_Checkbox" element on "Project_Settings_General_Tab" wizard + And wait load page + Then verify if "Notification_Popup" popup dialog appears + Then "Notification_Pop_Up" element on "Notification_Popup" should contains "Data was edited successfully" value + Then verify "Notification_Pop_Up_Cross_Close_Button" element visibility on "Notification_Popup" wizard + Then click on "Notification_Pop_Up_Cross_Close_Button" element on "Notification_Popup" wizard + And wait load page + Then click on "Notification_Pop_Up_Cross_Close_Button" element on "Notification_Popup" wizard + And wait load page + Then "Pull_At_Runtime_Checkbox" element should be checked on "Project_Settings_General_Tab" wizard + Then click on breadcrumbs "project" label on "commonPagesHeader" wizard + And wait load page + Then verify breadcrumbs "tab" label should be equal "Project monitoring" value + And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard + Then click on "Project_Settings_Button" element on "commonPagesHeader" wizard + And hover "MLRun_Logo" component on "commonPagesHeader" wizard + Then "Pull_At_Runtime_Checkbox" element should be checked on "Project_Settings_General_Tab" wizard + Then uncheck "Pull_At_Runtime_Checkbox" element on "Project_Settings_General_Tab" wizard + And wait load page + Then verify if "Notification_Popup" popup dialog appears + Then "Notification_Pop_Up" element on "Notification_Popup" should contains "Data was edited successfully" value + Then verify "Notification_Pop_Up_Cross_Close_Button" element visibility on "Notification_Popup" wizard + Then click on "Notification_Pop_Up_Cross_Close_Button" element on "Notification_Popup" wizard + And wait load page + Then "Pull_At_Runtime_Checkbox" element should be unchecked on "Project_Settings_General_Tab" wizard Then verify "Artifact_Path_Input" element visibility on "Project_Settings_General_Tab" wizard Then type value " " to "Artifact_Path_Input" field on "Project_Settings_General_Tab" wizard Then verify "Artifact_Path_Input" on "Project_Settings_General_Tab" wizard should display hover warning "Input_Hint"."Input_Field_Invalid" @@ -77,9 +105,8 @@ Feature: Project Settings page And click on "MLRun_Logo" element on "commonPagesHeader" wizard And wait load page Then type value "cat-vs-dog-classification" to "Search_Projects_Input" field on "Projects" wizard - Then value in "labels" column with "dropdowns" in "Projects_Table" on "Projects" wizard should contains "c=d" in "Overlay" - Then click on "Active_Projects_Button" element on "Projects" wizard - Then value in "labels" column with "dropdowns" in "Projects_Table" on "Projects" wizard should contains "e=f" in "Overlay" + Then value in "labels_key" column with "attribute_list" in "Projects_Table" on "Projects" wizard should contains "a,c,e" + Then value in "labels_value" column with "attribute_list" in "Projects_Table" on "Projects" wizard should contains "b,d,f" And click on row root with value "cat-vs-dog-classification" in "name" column in "Projects_Table" table on "Projects" wizard And wait load page And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard @@ -95,17 +122,15 @@ Feature: Project Settings page Then verify "Source_URL_Input" on "Project_Settings_General_Tab" wizard should display hover warning "Input_Hint"."Input_Field_Invalid" When add rows to "Labels_Table" table on "Project_Settings_General_Tab" wizard | key_input | value_input | - | a | b | - | project_label_key | project_label_value | | a12345 | b54321 | + | project_label_key | project_label_value | + | a | b | Then type value "test" to "Source_URL_Input" field on "Project_Settings_General_Tab" wizard And click on "MLRun_Logo" element on "commonPagesHeader" wizard And wait load page Then type value "cat-vs-dog-classification" to "Search_Projects_Input" field on "Projects" wizard - Then value in "labels" column with "dropdowns" in "Projects_Table" on "Projects" wizard should contains "project_label_key=project_label_value" in "Overlay" - Then click on "Active_Projects_Button" element on "Projects" wizard - Then value in "labels" column with "dropdowns" in "Projects_Table" on "Projects" wizard should contains "a12345=b54321" in "Overlay" - Then click on "Active_Projects_Button" element on "Projects" wizard + Then value in "labels_key" column with "attribute_list" in "Projects_Table" on "Projects" wizard should contains "a12345,project_label_key,a" + Then value in "labels_value" column with "attribute_list" in "Projects_Table" on "Projects" wizard should contains "b54321,project_label_value,b" And click on row root with value "cat-vs-dog-classification" in "name" column in "Projects_Table" table on "Projects" wizard And wait load page And hover "Project_Navigation_Toggler" component on "commonPagesHeader" wizard @@ -399,4 +424,4 @@ Feature: Project Settings page And select "Secrets" tab in "Project_Settings_Tab_Selector" on "Project_Settings_General_Tab" wizard And wait load page Then verify redirection from "projects/default/settings/INVALID" to "projects/default/settings/general" - Then verify redirection from "projects/default/INVALID/general" to "projects" \ No newline at end of file + Then verify redirection from "projects/default/INVALID/general" to "projects" diff --git a/tests/features/projectsPage.feature b/tests/features/projectsPage.feature index 826eb1eb3..d31bc4999 100644 --- a/tests/features/projectsPage.feature +++ b/tests/features/projectsPage.feature @@ -105,6 +105,7 @@ Feature: Projects Page Given open url And wait load page Then select "Delete" option in action menu on "Projects" wizard in "Projects_Table" table at row with "churn-project-admin" value in "name" column + And wait load page Then verify if "Common_Popup" popup dialog appears Then "Description" component on "Common_Popup" should be equal "Descriptions"."Delete_Project_Confirm_Message" Then verify "Cancel_Button" element visibility on "Common_Popup" wizard @@ -121,12 +122,13 @@ Feature: Projects Page Then verify if "Create_New_Project" popup dialog appears Then type into "Name_Input" on "Create_New_Project" popup dialog "empty-project" value Then type into "Description_Input" on "Create_New_Project" popup dialog "empty project description" value + And click on "Add_Label_Button" element on "Create_New_Project" wizard Then type value "empty" to "Labels_Key" field on "Create_New_Project" wizard Then type value "project" to "Labels_Value" field on "Create_New_Project" wizard When click on "Title" element on "Create_New_Project" wizard And wait load page Then click on "Create_Button" element on "Create_New_Project" wizard - And wait load page + Then wait for 4 seconds Then verify "Notification_Pop_Up" element visibility on "Notification_Popup" wizard Then "Notification_Pop_Up" element on "Notification_Popup" should contains "Project \"empty-project\" was created successfully" value And wait load page @@ -134,6 +136,8 @@ Feature: Projects Page Then click on "Notification_Pop_Up_Cross_Close_Button" element on "Notification_Popup" wizard And wait load page Then check "empty-project" value in "name" column in "Projects_Table" table on "Projects" wizard + Then type value "empty" to "Search_Projects_Input" field on "Projects" wizard + Then value in "name" column with "text" in "Projects_Table" on "Projects" wizard should contains "empty" Then select "Delete" option in action menu on "Projects" wizard in "Projects_Table" table at row with "empty-project" value in "name" column Then verify if "Common_Popup" popup dialog appears Then "Description" component on "Common_Popup" should be equal "Descriptions"."Delete_Project_Confirm_Message" @@ -384,13 +388,13 @@ Feature: Projects Page Then verify "Total_Counter_Title" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then "Total_Counter_Title" element in "Monitoring_Jobs_Box" on "Projects" should contains "Jobs" value Then verify "Total_Counter_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "19" value + Then "Total_Counter_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "20" value Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then verify "Counter_Running_Status_Icon" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then "Counter_Running_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "16" value Then verify "Counter_Failed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then verify "Counter_Failed_Status_Icon" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard - Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "2" value + Then "Counter_Failed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "3" value Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then verify "Counter_Completed_Status_Icon" element visibility in "Monitoring_Jobs_Box" on "Projects" wizard Then "Counter_Completed_Status_Number" element in "Monitoring_Jobs_Box" on "Projects" should contains "1" value @@ -436,7 +440,7 @@ Feature: Projects Page Then verify "Total_Counter_Title" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then "Total_Counter_Title" element in "Monitoring_Workflows_Box" on "Projects" should contains "Workflows" value Then verify "Total_Counter_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "2" value + Then "Total_Counter_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "3" value Then verify "Counter_Running_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then verify "Counter_Running_Status_Icon" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "0" value @@ -445,7 +449,7 @@ Feature: Projects Page Then "Counter_Failed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value Then verify "Counter_Completed_Status_Number" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then verify "Counter_Completed_Status_Icon" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard - Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "1" value + Then "Counter_Completed_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" should contains "2" value Then verify "See_All_Link" element visibility in "Monitoring_Workflows_Box" on "Projects" wizard Then "See_All_Link" element in "Monitoring_Workflows_Box" on "Projects" should contains "See all" value When click on "Counter_Running_Status_Number" element in "Monitoring_Workflows_Box" on "Projects" wizard diff --git a/tests/features/step-definitions/table.steps.js b/tests/features/step-definitions/table.steps.js index c27267706..55497c763 100644 --- a/tests/features/step-definitions/table.steps.js +++ b/tests/features/step-definitions/table.steps.js @@ -45,7 +45,8 @@ import { getCellByIndexColumn, getTableRows, isContainsSubstringInColumnCells, - isContainsSubstringInColumnAttributrCells, + isContainsSubstringInColumnAttributeCells, + isContainsSubstringInColumnAttributeListCells, isContainsSubstringInColumnDropdownCells, isContainsSubstringInColumnDropdownCellsOverlay, isContainsSubstringInColumnTooltipCells, @@ -1240,7 +1241,16 @@ Then( 'value in {string} column with {string} in {string} on {string} wizard should contains {string}', async function (column, type, table, wizard, substring) { if (type === 'attribute') { - await isContainsSubstringInColumnAttributrCells( + await isContainsSubstringInColumnAttributeCells( + this.driver, + pageObjects[wizard][table], + column, + substring + ) + } + + if (type === 'attribute_list') { + await isContainsSubstringInColumnAttributeListCells( this.driver, pageObjects[wizard][table], column, diff --git a/tests/mockServer/data/funcs.json b/tests/mockServer/data/funcs.json index 999d2be06..114e81727 100644 --- a/tests/mockServer/data/funcs.json +++ b/tests/mockServer/data/funcs.json @@ -30219,6 +30219,96 @@ }, "verbose": false, "status": {} - } + }, + { + "spec": { + "priority_class_name": "igz-workload-medium", + "command": "python your_training_script.py", + "state_thresholds": { + "pending_scheduled": "1h", + "pending_not_scheduled": "-1", + "image_pull_backoff": "1h", + "executing": "24h" + }, + "image": "your_image_with_horovod", + "description": "", + "replicas": 4, + "preemption_mode": "prevent", + "disable_auto_mount": true, + "build": { + "functionSourceCode": "IyBHZW5lcmF0ZWQgYnkgbnVjbGlvLmV4cG9ydC5OdWNsaW9FeHBvcnRlcgoKaW1wb3J0IG1scnVuCgojIENyZWF0ZSBvciBsb2FkIGFuIE1MUnVuIHByb2plY3QKcHJvamVjdCA9IG1scnVuLm5ld19wcm9qZWN0KCJob3Jvdm9kcHJvamVjdCIsIGNvbnRleHQ9Ii4vIiwgdXNlcl9wcm9qZWN0PVRydWUpCgojIERlZmluZSB0aGUgTVBJIGpvYiBmdW5jdGlvbgpmbiA9IG1scnVuLmNvZGVfdG9fZnVuY3Rpb24oCiAgICBuYW1lPSJob3Jvdm9kLXRyYWluIiwKICAgIGtpbmQ9Im1waWpvYiIsCiAgICBpbWFnZT0ieW91cl9pbWFnZV93aXRoX2hvcm92b2QiLAopCgojIFNldCBjb21tYW5kIGFuZCBvdGhlciBzcGVjcyBkaXJlY3RseQpmbi5zcGVjLmNvbW1hbmQgPSAicHl0aG9uIHlvdXJfdHJhaW5pbmdfc2NyaXB0LnB5Igpmbi5zcGVjLnJlcGxpY2FzID0gNAoKIyBSdW4gdGhlIGpvYgpydW4gPSBmbi5ydW4oKQoK", + "code_origin": "horovod-train.ipynb", + "origin_filename": "horovod-train.ipynb" + }, + "default_handler": "", + "clean_pod_policy": "All", + "mpi_args": [ + "-x", + "NCCL_SOCKET_NTHREADS=2", + "-x", + "NCCL_NSOCKS_PERTHREAD=8", + "-x", + "NCCL_MIN_NCHANNELS=4" + ], + "resources": { + "requests": { + "memory": "1Mi", + "cpu": "25m" + }, + "limits": { + "memory": "20Gi", + "cpu": "2" + } + }, + "env": [ + { + "name": "V3IO_API", + "value": "v3io-webapi.default-tenant.svc:8081" + }, + { + "name": "V3IO_USERNAME", + "value": "admin" + }, + { + "name": "V3IO_ACCESS_KEY", + "valueFrom": { + "secretKeyRef": { + "key": "accessKey", + "name": "mlrun-auth-secrets.352e93afc5b83672fc9aaf3f20478d0b82f572eaddf9a8f4bf71c51d" + } + } + }, + { + "name": "V3IO_FRAMESD", + "value": "framesd:8081" + }, + { + "name": "MLRUN_AUTH_SESSION", + "valueFrom": { + "secretKeyRef": { + "key": "accessKey", + "name": "mlrun-auth-secrets.f9557c6c45ee4666c5c1550dafeb323ef82127208de229b8ea3ae472" + } + } + } + ], + "affinity": null, + "node_selector": {}, + "tolerations": null + }, + "kind": "mpijob", + "verbose": false, + "metadata": { + "tag": "latest", + "credentials": { + "access_key": "$ref:mlrun-auth-secrets.f9557c6c45ee4666c5c1550dafeb323ef82127208de229b8ea3ae472" + }, + "name": "horovod-train", + "project": "sk-project-admin", + "hash": "c93f6c0bf4b3b633ef84c7c59093bd28b028e3a6", + "updated": "2024-10-08T14:06:44.563424+00:00" + }, + "status": {} + } ] } diff --git a/tests/mockServer/data/run.json b/tests/mockServer/data/run.json index d1563906e..264ef40ab 100644 --- a/tests/mockServer/data/run.json +++ b/tests/mockServer/data/run.json @@ -64564,6 +64564,51 @@ } ] } - } + }, + { + "kind": "run", + "metadata": { + "name": "horovod-train", + "uid": "85c3a6bcc292462d89e4d65444d179b1", + "iteration": 0, + "project": "sk-project-admin", + "labels": { + "v3io_user": "admin", + "kind": "mpijob", + "owner": "admin", + "mlrun/client_version": "1.7.0-rc51", + "mlrun/client_python_version": "3.9.18" + }, + "annotations": {} + }, + "spec": { + "function": "sk-project-admin/horovod-train@c93f6c0bf4b3b633ef84c7c59093bd28b028e3a6", + "log_level": "info", + "parameters": {}, + "handler": null, + "outputs": [], + "output_path": "v3io:///projects/sk-project-admin/artifacts", + "inputs": {}, + "notifications": [], + "state_thresholds": { + "pending_scheduled": "1h", + "pending_not_scheduled": "-1", + "image_pull_backoff": "1h", + "executing": "24h" + }, + "node_selector": {}, + "hyperparams": {}, + "hyper_param_options": {}, + "data_stores": [] + }, + "status": { + "results": {}, + "start_time": "2024-10-08T14:06:44.618450+00:00", + "last_update": "2024-10-08T14:34:12.621378+00:00", + "state": "aborted", + "abort_task_id": "74be3e06-56fb-4bc9-9ece-b77f7f818373", + "status_text": "aborted" + } + } ] } diff --git a/tests/mockServer/data/runs.json b/tests/mockServer/data/runs.json index 16b13dfa8..455f25972 100644 --- a/tests/mockServer/data/runs.json +++ b/tests/mockServer/data/runs.json @@ -64451,6 +64451,51 @@ } ] } - } + }, + { + "kind": "run", + "metadata": { + "name": "horovod-train", + "uid": "85c3a6bcc292462d89e4d65444d179b1", + "iteration": 0, + "project": "sk-project-admin", + "labels": { + "v3io_user": "admin", + "kind": "mpijob", + "owner": "admin", + "mlrun/client_version": "1.7.0-rc51", + "mlrun/client_python_version": "3.9.18" + }, + "annotations": {} + }, + "spec": { + "function": "sk-project-admin/horovod-train@c93f6c0bf4b3b633ef84c7c59093bd28b028e3a6", + "log_level": "info", + "parameters": {}, + "handler": null, + "outputs": [], + "output_path": "v3io:///projects/sk-project-admin/artifacts", + "inputs": {}, + "notifications": [], + "state_thresholds": { + "pending_scheduled": "1h", + "pending_not_scheduled": "-1", + "image_pull_backoff": "1h", + "executing": "24h" + }, + "node_selector": {}, + "hyperparams": {}, + "hyper_param_options": {}, + "data_stores": [] + }, + "status": { + "results": {}, + "start_time": "2024-10-08T14:06:44.618450+00:00", + "last_update": "2024-10-08T14:34:12.621378+00:00", + "state": "aborted", + "abort_task_id": "74be3e06-56fb-4bc9-9ece-b77f7f818373", + "status_text": "aborted" + } + } ] } diff --git a/tests/mockServer/dateSynchronization.js b/tests/mockServer/dateSynchronization.js index d04441c92..ff8f58db3 100644 --- a/tests/mockServer/dateSynchronization.js +++ b/tests/mockServer/dateSynchronization.js @@ -39,7 +39,7 @@ const nextHour = new Date(now.getTime() + (60 * 60 * 1000)) function updateRuns(runs) { // update start_time та last_update runs.runs = runs.runs.map(run => { - if (['cf842616c89347c7bb7bca2c9e840a21', '76f48c8165da473bb4356ef7b196343f','f5751299ee21476e897dfd90d94c49c4', 'dad0fdf93bd949589f6b20f79fa47798', '9723e5a30b0e43b0b7cfda098445c446'] + if (['cf842616c89347c7bb7bca2c9e840a21', '76f48c8165da473bb4356ef7b196343f','f5751299ee21476e897dfd90d94c49c4', 'dad0fdf93bd949589f6b20f79fa47798', '9723e5a30b0e43b0b7cfda098445c446', '85c3a6bcc292462d89e4d65444d179b1'] .includes(run.metadata.uid)) { return { ...run, status: { ...run.status, start_time: formatDate(oneDayAgo), last_update: formatDate(oneDayAgo) } diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index 80c8eb13a..348990a22 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -459,28 +459,51 @@ function deleteProject(req, res) { } function deleteProjectV2(req, res) { - const taskFunc = () => { - return new Promise(resolve => { - setTimeout( - () => { - deleteProjectHandler(req, res, true) + const isCascade = req.headers['x-mlrun-deletion-strategy'] === 'cascade' + const handleDeletion = () => { + const taskFunc = () => { + return new Promise((resolve) => { + setTimeout(() => { + deleteProjectHandler(req, res, true) resolve() - }, - random(5000, 10000) - ) + }, random(5000, 10000)) + }) + } + + const task = createTask(null, { + taskFunc, + kind: `project.deletion.wrapper.${req.params.project}`, }) + + res.status = 202 + res.send(task) } - const task = createTask(null, { - taskFunc, - kind: `project.deletion.wrapper.${req.params.project}` - }) - res.status = 202 + if (isCascade) { + handleDeletion() + } else { + const collectedProject = projects.projects.filter( + (project) => project.metadata.name === req.params['project'] + ) - res.send(task) -} + const isEmpty = collectedProject.every( + (project) => + (project.spec.functions && project.spec.functions.length > 0) || + (project.spec.workflows && project.spec.workflows.length > 0) || + (project.spec.artifacts && project.spec.artifacts.length > 0) + ) + if (!isEmpty) { + handleDeletion() + } else { + res.status(412).send({ + detail: `MLRunPreconditionFailedError('Project ${req.params.project} cannot be deleted since related resources found: artifacts')`, + }) + } + } +} + function patchProject(req, res) { const project = projects.projects.find(project => project.metadata.name === req.params['project']) @@ -1072,7 +1095,7 @@ function getProjectsFeaturesEntities(req, res) { if (artifact === 'feature-vectors' && item.metadata.labels) { return filterByLabels(item.metadata.labels, req.query['label']) } else if ((artifact === 'features' || artifact === 'entities') && item.labels) { - return filterByLabels(item.metadata.labels, req.query['label']) + return filterByLabels(item.labels, req.query['label']) } return false From 0992450214ce22b3198b3da08f7bd7f318bac8e2 Mon Sep 17 00:00:00 2001 From: mariana-furyk <58301139+mariana-furyk@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:38:14 +0200 Subject: [PATCH 02/26] Impl [Workflows] Add retry option for workflows (#2843) --- package.json | 2 +- src/actions/workflow.js | 5 +- src/api/workflow-api.js | 3 + .../MonitorWorkflows/monitorWorkflows.util.js | 12 +- src/elements/WorkflowsTable/WorkflowsTable.js | 173 +++++++++++------- 5 files changed, 123 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index b2d4eb6f4..40b7f09af 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "final-form-arrays": "^3.1.0", "fs-extra": "^10.0.0", "identity-obj-proxy": "^3.0.0", - "iguazio.dashboard-react-controls": "2.2.3", + "iguazio.dashboard-react-controls": "2.2.4", "is-wsl": "^1.1.0", "js-base64": "^2.5.2", "js-yaml": "^4.1.0", diff --git a/src/actions/workflow.js b/src/actions/workflow.js index df9428ee2..f7529b21b 100644 --- a/src/actions/workflow.js +++ b/src/actions/workflow.js @@ -73,7 +73,7 @@ const workflowActions = { dispatch(workflowActions.fetchWorkflowsBegin()) config?.ui?.setRequestErrorMessage?.('') - const errorHandler = (error) => { + const errorHandler = error => { dispatch(workflowActions.fetchWorkflowsFailure(error)) largeResponseCatchHandler( error, @@ -134,6 +134,9 @@ const workflowActions = { type: FETCH_WORKFLOWS_FAILURE, payload: error }), + rerunWorkflow: (project, workflowId) => () => { + return workflowApi.rerunWorkflow(project, workflowId) + }, resetWorkflow: () => ({ type: RESET_WORKFLOW }) diff --git a/src/api/workflow-api.js b/src/api/workflow-api.js index 18418b4af..8153c8c56 100644 --- a/src/api/workflow-api.js +++ b/src/api/workflow-api.js @@ -108,6 +108,9 @@ const workflowsApi = { } return mainHttpClient.get(`/projects/${project}/pipelines`, newConfig) + }, + rerunWorkflow: (project, workflowId) => { + return mainHttpClient.post(`projects/${project}/pipelines/${workflowId}/retry`) } } diff --git a/src/components/Jobs/MonitorWorkflows/monitorWorkflows.util.js b/src/components/Jobs/MonitorWorkflows/monitorWorkflows.util.js index 091e85ac9..4b5d272f5 100644 --- a/src/components/Jobs/MonitorWorkflows/monitorWorkflows.util.js +++ b/src/components/Jobs/MonitorWorkflows/monitorWorkflows.util.js @@ -57,6 +57,7 @@ import { ReactComponent as Run } from 'igz-controls/images/run.svg' import { ReactComponent as Cancel } from 'igz-controls/images/close.svg' import { ReactComponent as Yaml } from 'igz-controls/images/yaml.svg' import { ReactComponent as Delete } from 'igz-controls/images/delete.svg' +import { ReactComponent as Rerun } from 'igz-controls/images/rerun.svg' export const generateFilters = () => [ { @@ -102,7 +103,8 @@ export const generateActionsMenu = ( abortable_function_kinds, handleConfirmAbortJob, handleConfirmDeleteJob, - toggleConvertedYaml + toggleConvertedYaml, + handleRerun ) => { if (job?.uid) { const jobKindIsAbortable = isJobKindAbortable(job, abortable_function_kinds) @@ -155,12 +157,20 @@ export const generateActionsMenu = ( ] ] } else { + const runningStates = ['running', 'pending'] + return [ [ { label: 'View YAML', icon: , onClick: toggleConvertedYaml + }, + { + hidden: runningStates.includes(job?.state?.value), + icon: , + label: 'Retry', + onClick: () => handleRerun(job) } ] ] diff --git a/src/elements/WorkflowsTable/WorkflowsTable.js b/src/elements/WorkflowsTable/WorkflowsTable.js index 9ef603bd4..8e731b884 100644 --- a/src/elements/WorkflowsTable/WorkflowsTable.js +++ b/src/elements/WorkflowsTable/WorkflowsTable.js @@ -185,7 +185,14 @@ const WorkflowsTable = React.forwardRef( replace: true }) }) - }, [backLink, dispatch, navigate, params.projectName, params.workflowId, params.workflowProjectName]) + }, [ + backLink, + dispatch, + navigate, + params.projectName, + params.workflowId, + params.workflowProjectName + ]) const handlePollAbortingJob = useCallback( (jobRun, refresh) => { @@ -233,60 +240,62 @@ const WorkflowsTable = React.forwardRef( if (workflowsStore.activeWorkflow?.data) { const workflow = { ...workflowsStore.activeWorkflow.data } - return find(workflow.graph, workflowItem => - workflowItem.run_type === 'run' && workflowItem.run_uid === params.jobId) + return find( + workflow.graph, + workflowItem => workflowItem.run_type === 'run' && workflowItem.run_uid === params.jobId + ) } }, [params.jobId, workflowsStore.activeWorkflow.data]) - const getPipelineError = useCallback(isErrorState => { - return isErrorState && - workflowsStore.activeWorkflow?.data?.run?.error && - workflowsStore.activeWorkflow.data.run.error !== 'None' ? { - title: 'Pipeline error - ', - message: workflowsStore.activeWorkflow.data.run.error - } : {} - }, [workflowsStore.activeWorkflow.data]) - - const fetchRun = useCallback( - () => { - return dispatch( - jobsActions.fetchJob(params.workflowProjectName || params.projectName, params.jobId) - ) - .then(job => { - const selectedJob = findSelectedWorkflowJob() - const graphJobState = selectedJob?.phase?.toLowerCase() - const isErrorState = [FAILED_STATE, ERROR_STATE].includes(graphJobState) - const customJobState = isErrorState ? graphJobState : '' - - return modifyAndSelectRun(parseJob( - job, - MONITOR_WORKFLOWS_TAB, - customJobState, - getPipelineError(isErrorState) - ), fetchRun) - }) - .catch(() => - navigate(backLink, { - replace: true - }) - ) - .finally(() => { - fetchJobFunctionsPromiseRef.current = null - }) + const getPipelineError = useCallback( + isErrorState => { + return isErrorState && + workflowsStore.activeWorkflow?.data?.run?.error && + workflowsStore.activeWorkflow.data.run.error !== 'None' + ? { + title: 'Pipeline error - ', + message: workflowsStore.activeWorkflow.data.run.error + } + : {} }, - [ - backLink, - dispatch, - findSelectedWorkflowJob, - modifyAndSelectRun, - navigate, - params.jobId, - params.projectName, - params.workflowProjectName, - getPipelineError - ] + [workflowsStore.activeWorkflow.data] ) + const fetchRun = useCallback(() => { + return dispatch( + jobsActions.fetchJob(params.workflowProjectName || params.projectName, params.jobId) + ) + .then(job => { + const selectedJob = findSelectedWorkflowJob() + const graphJobState = selectedJob?.phase?.toLowerCase() + const isErrorState = [FAILED_STATE, ERROR_STATE].includes(graphJobState) + const customJobState = isErrorState ? graphJobState : '' + + return modifyAndSelectRun( + parseJob(job, MONITOR_WORKFLOWS_TAB, customJobState, getPipelineError(isErrorState)), + fetchRun + ) + }) + .catch(() => + navigate(backLink, { + replace: true + }) + ) + .finally(() => { + fetchJobFunctionsPromiseRef.current = null + }) + }, [ + backLink, + dispatch, + findSelectedWorkflowJob, + modifyAndSelectRun, + navigate, + params.jobId, + params.projectName, + params.workflowProjectName, + getPipelineError + ]) + const setJobStatusAborting = useCallback( task => { setSelectedJob(state => ({ @@ -328,7 +337,7 @@ const WorkflowsTable = React.forwardRef(
Are you sure you want to abort the job "{job.name}"?
{isJobKindLocal(job) && - 'This is a local run. You can abort the run, though the actual process will continue.'} + 'This is a local run. You can abort the run, though the actual process will continue.'}
), btnConfirmLabel: 'Abort', @@ -381,6 +390,27 @@ const WorkflowsTable = React.forwardRef( [onDeleteJob, setConfirmData] ) + const handleRerun = useCallback( + workflow => { + dispatch(workflowsActions.rerunWorkflow(workflow.project, workflow.id)) + .then( + dispatch( + setNotification({ + status: 200, + id: Math.random(), + message: 'Workflow ran successfully.' + }) + ) + ) + .catch(error => { + showErrorNotification(dispatch, error, 'Workflow did not run successfully', '', () => + handleRerun(workflow) + ) + }) + }, + [dispatch] + ) + const actionsMenu = useMemo(() => { return job => generateActionsMenu( @@ -391,7 +421,8 @@ const WorkflowsTable = React.forwardRef( appStore.frontendSpec.abortable_function_kinds, handleConfirmAbortJob, handleConfirmDeleteJob, - toggleConvertedYaml + toggleConvertedYaml, + handleRerun ) }, [ handleRerunJob, @@ -400,7 +431,8 @@ const WorkflowsTable = React.forwardRef( handleMonitoring, handleConfirmAbortJob, handleConfirmDeleteJob, - toggleConvertedYaml + toggleConvertedYaml, + handleRerun ]) const handleCancel = useCallback(() => { @@ -409,25 +441,27 @@ const WorkflowsTable = React.forwardRef( setItemIsSelected(false) }, [setItemIsSelected, setSelectedFunction, setSelectedJob]) - const findSelectedWorkflowFunction = useCallback((withoutRunType) => { - if (workflowsStore.activeWorkflow?.data) { - const workflow = { ...workflowsStore.activeWorkflow.data } + const findSelectedWorkflowFunction = useCallback( + withoutRunType => { + if (workflowsStore.activeWorkflow?.data) { + const workflow = { ...workflowsStore.activeWorkflow.data } - return find(workflow.graph, workflowItem => { - let workflowItemIsFound = ( - workflowItem.function?.includes(`${params.functionName}@${params.functionHash}`) || - workflowItem.function?.includes(params.functionName) || - workflowItem.function?.includes(params.jobId) - ) + return find(workflow.graph, workflowItem => { + let workflowItemIsFound = + workflowItem.function?.includes(`${params.functionName}@${params.functionHash}`) || + workflowItem.function?.includes(params.functionName) || + workflowItem.function?.includes(params.jobId) - if (withoutRunType) { - workflowItemIsFound = workflowItemIsFound && workflowItem.run_type !== 'run' - } + if (withoutRunType) { + workflowItemIsFound = workflowItemIsFound && workflowItem.run_type !== 'run' + } - return workflowItemIsFound - }) - } - }, [params.functionName, params.functionHash, params.jobId, workflowsStore.activeWorkflow.data]) + return workflowItemIsFound + }) + } + }, + [params.functionName, params.functionHash, params.jobId, workflowsStore.activeWorkflow.data] + ) const checkIfWorkflowItemIsJob = useCallback(() => { if (workflowsStore.activeWorkflow?.data?.graph) { @@ -459,7 +493,8 @@ const WorkflowsTable = React.forwardRef( !fetchJobFunctionsPromiseRef.current && params.jobId && (isEmpty(selectedJob) || params.jobId !== selectedJob.uid) && - checkIfWorkflowItemIsJob() && !dataIsLoading + checkIfWorkflowItemIsJob() && + !dataIsLoading ) { setDataIsLoading(true) fetchRun().finally(() => setDataIsLoading(false)) From 7ac35cea1ab8bdff218b2e9c4a304cdfd0fd788f Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:38:30 +0200 Subject: [PATCH 03/26] Fix [Project monitoring] Register model in demo mode with Unexpected Application Error (#2844) --- src/components/Project/ProjectMonitor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Project/ProjectMonitor.js b/src/components/Project/ProjectMonitor.js index 6d611cf29..73f847709 100644 --- a/src/components/Project/ProjectMonitor.js +++ b/src/components/Project/ProjectMonitor.js @@ -107,10 +107,10 @@ const ProjectMonitor = ({ const openRegisterModelModal = useCallback(() => { openPopUp(RegisterModelModal, { - projectName: params.projectName, + params: params, refresh: () => navigate(registerArtifactLink(MODEL_TYPE)) }) - }, [params.projectName, navigate, registerArtifactLink]) + }, [params, navigate, registerArtifactLink]) const { createNewOptions } = useMemo(() => { const createNewOptions = generateCreateNewOptions( From 79c1ee75a68581780d653dad3add528070e3f2dd Mon Sep 17 00:00:00 2001 From: Taras-Hlukhovetskyi <155433425+Taras-Hlukhovetskyi@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:38:45 +0200 Subject: [PATCH 04/26] Update mock for filtering workflows by name (#2848) --- tests/mockServer/mock.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index 348990a22..a9a2faa65 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -1353,6 +1353,7 @@ function getPipelines(req, res) { if (req.params['project'] === '*') { const pipelinesRun = pipelineIDs.map(pipeline => pipeline.run) const filter = JSON.parse(req.query.filter) + const nameFilter = req.query['name-contains'] const predicates = filter.predicates if (!predicates.length) { @@ -1362,28 +1363,29 @@ function getPipelines(req, res) { next_page_token: null }) } - - let queryTimestampValue, queryStateValue - - if (predicates.length === 1) { - queryTimestampValue = predicates[0].timestamp_value - queryStateValue = predicates[0].string_values ? predicates[0].string_values.values : null - } else { - queryTimestampValue = predicates[1].timestamp_value - queryStateValue = predicates[0].string_values.values - } + const queryFromTimestampValue = predicates.find( + predicate => predicate.key === 'created_at' && predicate.op === 5 + )?.timestamp_value + const queryToTimestampValue = + predicates.find(predicate => predicate.key === 'created_at' && predicate.op === 7) + ?.timestamp_value ?? new Date() + const queryStateValue = predicates.find(predicate => predicate.key === 'status')?.string_values + ?.values const collectedMonitoringPipelines = pipelinesRun.filter(pipeline => { const pipelineCreatedAt = new Date(pipeline.created_at) const timestampMatch = - !queryTimestampValue || pipelineCreatedAt >= new Date(queryTimestampValue) + !queryFromTimestampValue || + (pipelineCreatedAt >= new Date(queryFromTimestampValue) && + pipelineCreatedAt <= new Date(queryToTimestampValue)) const stateMatch = queryStateValue ? Array.isArray(queryStateValue) ? queryStateValue.includes(pipeline.status) : pipeline.status === queryStateValue : true + const nameMatch = nameFilter ? pipeline.name.includes(nameFilter) : true - return timestampMatch && stateMatch + return timestampMatch && stateMatch && nameMatch }) res.send({ @@ -1412,6 +1414,13 @@ function getPipelines(req, res) { } } + if (req.query['name-contains']) { + const nameFilter = req.query['name-contains'] + collectedPipelines.runs = collectedPipelines.runs.filter(pipeline => { + return pipeline.name.includes(nameFilter) + }) + } + res.send(collectedPipelines) } From 45c3d215e40f90bfda2fe3ae430e034b98a43c24 Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:38:54 +0200 Subject: [PATCH 05/26] Fix [Settings] Change Node Selectors more info message (#2849) --- .../ProjectSettingsGeneral/ProjectSettingsGeneral.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js b/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js index 3c35572a0..7dfb1aa86 100644 --- a/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js +++ b/src/elements/ProjectSettingsGeneral/ProjectSettingsGeneral.js @@ -258,10 +258,7 @@ const ProjectSettingsGeneral = ({ label="Pull at runtime" name={LOAD_SOURCE_ON_RUN} /> - +
Node Selectors - +
Date: Fri, 1 Nov 2024 13:31:07 +0200 Subject: [PATCH 06/26] Tests [Mock] Add mock for overwriting artifacts (#2858) * Add mock for overwriting artifacts * fix comments --- tests/mockServer/mock.js | 161 ++++++++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 45 deletions(-) diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index a9a2faa65..1f1f8cb67 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -160,7 +160,8 @@ const projectExistsConflict = { detail: "MLRunConflictError('Conflict - Project already exists')" } const projectsLimitReachedConflict = { - detail: "MLRunHTTPError(\"Failed creating project in Iguazio: [{'status': 405, 'detail': 'Resource limit reached. Cannot create more records'}]\")" + detail: + "MLRunHTTPError(\"Failed creating project in Iguazio: [{'status': 405, 'detail': 'Resource limit reached. Cannot create more records'}]\")" } const secretKeyTemplate = { provider: 'kubernetes', @@ -308,7 +309,9 @@ function deleteProjectHandler(req, res, omitResponse) { function filterByLabels(elementLabels, requestLabels) { if (requestLabels?.length > 0 && !isEmpty(elementLabels)) { - const requestLabelsList = (isArray(requestLabels) ? requestLabels : [requestLabels]).map(label => label.split('=')) + const requestLabelsList = (isArray(requestLabels) ? requestLabels : [requestLabels]).map( + label => label.split('=') + ) return requestLabelsList.every(([key = '', value = '']) => { const trimmedKey = key.trim() @@ -319,10 +322,15 @@ function filterByLabels(elementLabels, requestLabels) { } if (trimmedValue && trimmedValue.startsWith('~')) { - return elementLabels[trimmedKey] && elementLabels[trimmedKey].toLowerCase().includes(trimmedValue.substring(1).toLowerCase()) + return ( + elementLabels[trimmedKey] && + elementLabels[trimmedKey].toLowerCase().includes(trimmedValue.substring(1).toLowerCase()) + ) } - return elementLabels[trimmedKey] && (!trimmedValue || elementLabels[trimmedKey] === trimmedValue) + return ( + elementLabels[trimmedKey] && (!trimmedValue || elementLabels[trimmedKey] === trimmedValue) + ) }) } @@ -372,7 +380,9 @@ function getFeatureSet(req, res) { } if (req.query['label']) { - collectedFeatureSets = collectedFeatureSets.filter(featureSet => filterByLabels(featureSet.metadata.labels, req.query['label'])) + collectedFeatureSets = collectedFeatureSets.filter(featureSet => + filterByLabels(featureSet.metadata.labels, req.query['label']) + ) } res.send({ feature_sets: collectedFeatureSets }) @@ -463,17 +473,20 @@ function deleteProjectV2(req, res) { const handleDeletion = () => { const taskFunc = () => { - return new Promise((resolve) => { - setTimeout(() => { - deleteProjectHandler(req, res, true) - resolve() - }, random(5000, 10000)) + return new Promise(resolve => { + setTimeout( + () => { + deleteProjectHandler(req, res, true) + resolve() + }, + random(5000, 10000) + ) }) } const task = createTask(null, { taskFunc, - kind: `project.deletion.wrapper.${req.params.project}`, + kind: `project.deletion.wrapper.${req.params.project}` }) res.status = 202 @@ -484,11 +497,11 @@ function deleteProjectV2(req, res) { handleDeletion() } else { const collectedProject = projects.projects.filter( - (project) => project.metadata.name === req.params['project'] + project => project.metadata.name === req.params['project'] ) const isEmpty = collectedProject.every( - (project) => + project => (project.spec.functions && project.spec.functions.length > 0) || (project.spec.workflows && project.spec.workflows.length > 0) || (project.spec.artifacts && project.spec.artifacts.length > 0) @@ -498,12 +511,12 @@ function deleteProjectV2(req, res) { handleDeletion() } else { res.status(412).send({ - detail: `MLRunPreconditionFailedError('Project ${req.params.project} cannot be deleted since related resources found: artifacts')`, + detail: `MLRunPreconditionFailedError('Project ${req.params.project} cannot be deleted since related resources found: artifacts')` }) } } } - + function patchProject(req, res) { const project = projects.projects.find(project => project.metadata.name === req.params['project']) @@ -723,17 +736,17 @@ function getRuns(req, res) { .filter(run => { const runStartTime = new Date(run.status.start_time) - if (!start_time_from || runStartTime >= new Date(start_time_from)) { - if (state) { - if (isArray(state)) { - return state.includes(run.status.state) + if (!start_time_from || runStartTime >= new Date(start_time_from)) { + if (state) { + if (isArray(state)) { + return state.includes(run.status.state) + } else { + return run.status.state === state + } } else { - return run.status.state === state + return true } - } else { - return true } - } return false }) @@ -761,7 +774,9 @@ function getRuns(req, res) { } if (req.query['label']) { - collectedRuns = collectedRuns.filter(run => filterByLabels(run.metadata.labels, req.query['label'])) + collectedRuns = collectedRuns.filter(run => + filterByLabels(run.metadata.labels, req.query['label']) + ) } if (req.query['partition-by'] && req.query['partition-sort-by']) { @@ -899,7 +914,9 @@ function getProjectsSchedules(req, res) { } if (req.query['label']) { - collectedSchedules = collectedSchedules.filter(schedule => filterByLabels(schedule.labels, req.query['label'])) + collectedSchedules = collectedSchedules.filter(schedule => + filterByLabels(schedule.labels, req.query['label']) + ) } res.send({ schedules: collectedSchedules }) @@ -1176,7 +1193,9 @@ function getArtifacts(req, res) { } if (req.query['label']) { - collectedArtifacts = collectedArtifacts.filter(artifact => filterByLabels(artifact.metadata.labels, req.query['label'])) + collectedArtifacts = collectedArtifacts.filter(artifact => + filterByLabels(artifact.metadata.labels, req.query['label']) + ) } if (req.query['name']) { @@ -1215,17 +1234,17 @@ function getArtifacts(req, res) { if (req.query['format'] === 'minimal') { collectedArtifacts = collectedArtifacts.map(func => { - const fieldsToPick = [ - 'db_key', - 'producer', - 'size', - 'target_path', - 'framework', - 'metrics' - ] + const fieldsToPick = ['db_key', 'producer', 'size', 'target_path', 'framework', 'metrics'] const specFieldsToPick = fieldsToPick.map(fieldName => `spec.${fieldName}`) - return pick(func, ['kind', 'metadata', 'status', 'project', ...specFieldsToPick, ...fieldsToPick]) + return pick(func, [ + 'kind', + 'metadata', + 'status', + 'project', + ...specFieldsToPick, + ...fieldsToPick + ]) }) } @@ -1313,13 +1332,12 @@ function patchProjectsFeatureVectors(req, res) { } function getProjectsFeatureVector(req, res) { - const featureVector = featureVectors.feature_vectors - .find( - item => - item.metadata.project === req.params.project && - item.metadata.name === req.params.name && - (item.metadata.uid === req.params.reference || item.metadata.tag === req.params.reference) - ) + const featureVector = featureVectors.feature_vectors.find( + item => + item.metadata.project === req.params.project && + item.metadata.name === req.params.name && + (item.metadata.uid === req.params.reference || item.metadata.tag === req.params.reference) + ) if (featureVector) { res.send(featureVector) @@ -1706,7 +1724,7 @@ function getFileStats(req, res) { const { size } = fs.statSync(filePath) const mimeType = mime.lookup(filePath) - res.send({mimetype: mimeType, size, modified: Date.now()}) + res.send({ mimetype: mimeType, size, modified: Date.now() }) } function deleteSchedule(req, res) { @@ -1886,7 +1904,8 @@ function putTags(req, res) { return ( artifactMetaData?.project === req.params.project && - (artifact.kind === req.body.identifiers[0].kind || (!artifact.kind && req.body.identifiers[0].kind === 'artifact')) && + (artifact.kind === req.body.identifiers[0].kind || + (!artifact.kind && req.body.identifiers[0].kind === 'artifact')) && (artifactMetaData?.uid === req.body.identifiers[0].uid || artifactMetaData?.tree === req.body.identifiers[0].uid) && artifactSpecData?.db_key === req.body.identifiers[0].key @@ -2016,6 +2035,37 @@ function postArtifact(req, res) { artifactTemplate.spec.model_file = req.body.spec.model_file } + + const collectedArtifactsWithSameName = artifacts.artifacts.filter(artifact => { + return ( + artifact.metadata?.project === req.body.metadata.project && + ((artifact.spec && artifact.spec.db_key === req.body.spec.db_key) || + artifact.metadata.key === req.body.metadata.key) + ) + }) + + collectedArtifactsWithSameName.forEach(artifact => { + if (artifact.metadata?.tag === req.body.metadata.tag) { + // override existing artifact's tag in case when we create artifact with same tag + artifact.metadata.tag = null + } else if (artifact.metadata?.tag === 'latest') { + // when we post an artifact with custom tag we store 2 artifacts (custom and latest) + // so when we post another artifact with same name we have to delete artifact with 'latest' tag + // or we remove latest tag in case when we have only one object + if ( + collectedArtifactsWithSameName.find( + searchedArtifact => + searchedArtifact.metadata.uid === artifact.metadata.uid && + searchedArtifact.metadata?.tag !== 'latest' + ) + ) { + remove(artifacts.artifacts, artifact) + } else { + artifact.metadata.tag = null + } + } + }) + if (artifactTag === 'latest') { artifactTemplate.metadata['tag'] = artifactTag artifacts.artifacts.push(artifactTemplate) @@ -2084,6 +2134,24 @@ function deleteArtifact(req, res) { res.send({}) } +function deleteArtifacts(req, res) { + const collectedArtifacts = artifacts.artifacts.filter(artifact => { + const artifactMetaData = artifact.metadata ?? artifact + const artifactSpecData = artifact.spec ?? artifact + + return ( + artifactMetaData?.project === req.params.project && + (artifactSpecData?.db_key === req.query.name || artifactMetaData.key === req.query.name) + ) + }) + + if (collectedArtifacts?.length > 0) { + collectedArtifacts.forEach(collectedArtifact => remove(artifacts.artifacts, collectedArtifact)) + } + + res.send({}) +} + function getModelEndpoints(req, res) { let collectedEndpoints = modelEndpoints.endpoints .filter(endpoint => endpoint.metadata.project === req.params.project) @@ -2096,7 +2164,9 @@ function getModelEndpoints(req, res) { } })) if (req.query['label']) { - collectedEndpoints = collectedEndpoints.filter(endpoint => filterByLabels(endpoint.metadata.labels, req.query['label'])) + collectedEndpoints = collectedEndpoints.filter(endpoint => + filterByLabels(endpoint.metadata.labels, req.query['label']) + ) } res.send({ endpoints: collectedEndpoints }) @@ -2477,6 +2547,7 @@ app.get(`${mlrunAPIIngressV2}/projects/:project/artifacts/:key`, getArtifact) app.post(`${mlrunAPIIngressV2}/projects/:project/artifacts`, postArtifact) app.put(`${mlrunAPIIngressV2}/projects/:project/artifacts/:key`, putArtifact) app.delete(`${mlrunAPIIngressV2}/projects/:project/artifacts/:key`, deleteArtifact) +app.delete(`${mlrunAPIIngressV2}/projects/:project/artifacts`, deleteArtifacts) app.put(`${mlrunAPIIngress}/projects/:project/tags/:tag`, putTags) app.delete(`${mlrunAPIIngress}/projects/:project/tags/:tag`, deleteTags) From 6431995a588b3fb26fd65e446e01e4d17ffcffde Mon Sep 17 00:00:00 2001 From: Taras-Hlukhovetskyi <155433425+Taras-Hlukhovetskyi@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:31:27 +0200 Subject: [PATCH 07/26] Tests [Mock] Update the mock with link redirection to a non-existent project (#2857) --- tests/mockServer/mock.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index 1f1f8cb67..a1a973c9a 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -1413,7 +1413,9 @@ function getPipelines(req, res) { }) } //get pipelines for Jobs and workflows page Monitor Workflows tab - const collectedPipelines = { ...pipelines[req.params.project] } + const collectedPipelines = { + ...(pipelines[req.params.project] ?? { runs: [], total_size: 0, next_page_token: null }) + } if (req.query.filter) { const nameFilter = JSON.parse(req.query.filter).predicates.find(item => item.key === 'name') @@ -1443,7 +1445,17 @@ function getPipelines(req, res) { } function getPipeline(req, res) { - const collectedPipeline = pipelineIDs.find(item => item.run.id === req.params.pipelineID) + const collectedPipeline = pipelineIDs.find( + item => item.run.id === req.params.pipelineID && item.run.project === req.params.project + ) + + if (!collectedPipeline) { + res.statusCode = 404 + + return res.send({ + detail: `"MLRunNotFoundError('Pipeline run with id ${req.params.pipelineID} is not of project ${req.params.project}')"` + }) + } res.send(collectedPipeline) } From 2b57f3ec3d34b3e2dae19af3cb94c2c7bf24b531 Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:31:38 +0200 Subject: [PATCH 08/26] Fix [Projects] Request 500 error message (#2856) * Fix [Projects] Request 500 error message * Fix [Projects] Request 500 error message --- src/actions/projects.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/actions/projects.js b/src/actions/projects.js index 53cda6384..fe2d4c759 100644 --- a/src/actions/projects.js +++ b/src/actions/projects.js @@ -580,23 +580,25 @@ const projectsAction = { return summaryData }) .catch(err => { - if (!firstServerErrorTimestamp) { - firstServerErrorTimestamp = new Date() + if (mlrunUnhealthyErrors.includes(err.response?.status)) { + if (!firstServerErrorTimestamp) { + firstServerErrorTimestamp = new Date() - dispatch(projectsAction.setMlrunUnhealthyRetrying(true)) - } + dispatch(projectsAction.setMlrunUnhealthyRetrying(true)) + } - const threeMinutesPassed = (new Date() - firstServerErrorTimestamp) / 1000 > 180 + const threeMinutesPassed = (new Date() - firstServerErrorTimestamp) / 1000 > 180 - if (mlrunUnhealthyErrors.includes(err.response?.status) && !threeMinutesPassed) { - setTimeout(() => { - dispatch(projectsAction.fetchProjectsSummary(signal, refresh)) - }, 3000) - } + if (!threeMinutesPassed) { + setTimeout(() => { + dispatch(projectsAction.fetchProjectsSummary(signal, refresh)) + }, 3000) + } - if (threeMinutesPassed) { - dispatch(projectsAction.setMlrunIsUnhealthy(true)) - dispatch(projectsAction.setMlrunUnhealthyRetrying(true)) + if (threeMinutesPassed) { + dispatch(projectsAction.setMlrunIsUnhealthy(true)) + dispatch(projectsAction.setMlrunUnhealthyRetrying(true)) + } } dispatch(projectsAction.fetchProjectsSummaryFailure(err)) From c607232e3819b993e0d0c7e675fe2dcfe1c56bce Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:31:56 +0200 Subject: [PATCH 09/26] Tests [Mock] Update mock for creating a function from UI (#2855) --- tests/mockServer/mock.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index a1a973c9a..c4cf5d2f4 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -1572,6 +1572,10 @@ function postFunc(req, res) { baseFunc.metadata.hash = hashPwd baseFunc.status = {} + if (!baseFunc.metadata.tag) { + baseFunc.metadata.tag = 'latest' + } + funcs.funcs.push(baseFunc) res.send({ hash_key: hashPwd }) @@ -2609,7 +2613,6 @@ app.post(`${mlrunAPIIngress}/build/function`, deployMLFunction) app.get(`${mlrunAPIIngress}/projects/:project/files`, getFile) app.get(`${mlrunAPIIngress}/projects/:project/filestat`, getFileStats) - app.get(`${mlrunAPIIngress}/log/:project/:uid`, getLog) app.get(`${mlrunAPIIngress}/projects/:project/runtime-resources`, getRuntimeResources) From 3c64a0764077183b4c546274ef033e5228880c25 Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:32:08 +0200 Subject: [PATCH 10/26] Tests [Mock] Add mock for 'unhealthy' mlrun message appear (#2854) --- src/httpClient.js | 8 +- tests/features/common-tools/common-tools.js | 23 +- tests/features/common/page-objects.js | 1 + .../page-objects/interactive-popup.po.js | 529 ++++++++++-------- .../common/page-objects/projects.po.js | 179 ++++-- tests/features/projectsPage.feature | 38 +- tests/features/support/hooks.js | 25 +- tests/mockServer/mock.js | 20 + 8 files changed, 515 insertions(+), 308 deletions(-) diff --git a/src/httpClient.js b/src/httpClient.js index 021f0ac93..e758ea10e 100755 --- a/src/httpClient.js +++ b/src/httpClient.js @@ -129,7 +129,10 @@ const responseFulfillInterceptor = response => { delete requestTimeouts[response.config.ui.requestId] if (isLargeResponse) { - showLargeResponsePopUp(response.config.ui.setRequestErrorMessage, response.config.ui.customErrorMessage) + showLargeResponsePopUp( + response.config.ui.setRequestErrorMessage, + response.config.ui.customErrorMessage + ) throw new Error(LARGE_REQUEST_CANCELED) } else { @@ -174,7 +177,8 @@ mainHttpClientV2.interceptors.response.use(responseFulfillInterceptor, responseR export const showLargeResponsePopUp = (setRequestErrorMessage, customErrorMessage) => { if (!largeResponsePopUpIsOpen) { - const errorMessage = customErrorMessage || + const errorMessage = + customErrorMessage || 'The query result is too large to display. Add a filter (or narrow it) to retrieve fewer results.' setRequestErrorMessage(errorMessage) diff --git a/tests/features/common-tools/common-tools.js b/tests/features/common-tools/common-tools.js index c36c0796b..4383c2891 100644 --- a/tests/features/common-tools/common-tools.js +++ b/tests/features/common-tools/common-tools.js @@ -17,6 +17,7 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ +import axios from 'axios' import { generateRegEx, getLength, getNotToBe, getRule } from './utils' import { deleteAPIArtifact, @@ -39,10 +40,16 @@ module.exports = { return result.join('') } }, - generateInputGroup: function (root, label = false, hint = false, warning = false, input = 'input') { + generateInputGroup: function ( + root, + label = false, + hint = false, + warning = false, + input = 'input' + ) { const structure = { root, elements: {} } structure.elements.input = input - + if (label) { structure.elements.label = 'label' } @@ -81,7 +88,7 @@ module.exports = { if (warning) { structure.elements.warningHint = typeof warning === 'string' ? warning : '.range__warning svg' structure.elements.warningText = '.tooltip .tooltip__text' - } + } return structure }, @@ -187,5 +194,15 @@ module.exports = { return null } }) + }, + setRequestsFailureCondition: async shouldFail => { + try { + const response = await axios.post('http://localhost:30000/set-failure-condition', { + shouldFail + }) + console.log(response.data) + } catch (error) { + console.error(`Error setting failure condition: ${error}`) + } } } diff --git a/tests/features/common/page-objects.js b/tests/features/common/page-objects.js index 432e42fa1..87defd53f 100644 --- a/tests/features/common/page-objects.js +++ b/tests/features/common/page-objects.js @@ -73,6 +73,7 @@ module.exports = { Metrics_Selector_Popup: interactivePopup['metricsSelectorPopup'], ML_Function_Info_Pane: infoPane['mlFunctionInfoPane'], ML_Functions: Functions['mlFunctions'], + MLRun_Unhealthy_PopUp: interactivePopup['mLRunUnhealthyPopUp'], Model_Endpoints: models['modelEndpoints'], Models: models['modelsTab'], Models_Info_Pane: infoPane['modelsInfoPane'], diff --git a/tests/features/common/page-objects/interactive-popup.po.js b/tests/features/common/page-objects/interactive-popup.po.js index dbe3e4095..d06cad250 100644 --- a/tests/features/common/page-objects/interactive-popup.po.js +++ b/tests/features/common/page-objects/interactive-popup.po.js @@ -137,10 +137,10 @@ const artifactsPreviewRow = { } const artifactsPreviewHeader = { - root: '.pop-up-dialog', + root: '.pop-up-dialog', header: {}, body: { - root: '.preview-body', + root: '.preview-body', row: { root: '.preview-item:nth-of-type(1)', fields: { @@ -189,8 +189,7 @@ const artifactsLabelsTable = { } const labelsTable = { - root: - '.job-wizard__run-details .form-row:nth-of-type(4) .chips', + root: '.job-wizard__run-details .form-row:nth-of-type(4) .chips', header: {}, body: { root: '.chips-wrapper', @@ -209,8 +208,7 @@ const labelsTable = { } const newProjectLabelsTable = { - root: - '.create-project-dialog .form-row:nth-of-type(4) .chips', + root: '.create-project-dialog .form-row:nth-of-type(4) .chips', header: {}, body: { root: '.chips-wrapper', @@ -229,10 +227,10 @@ const newProjectLabelsTable = { } const dataInputsHeaders = { - root: '.wizard-form__content [data-testid="dataInputs.dataInputsTable"]', + root: '.wizard-form__content [data-testid="dataInputs.dataInputsTable"]', header: {}, body: { - root: '.form-table__header-row', + root: '.form-table__header-row', row: { root: '.form-table__cell', fields: { @@ -243,10 +241,10 @@ const dataInputsHeaders = { } const parametersHeaders = { - root: '.wizard-form__content [data-testid="parameters.parametersTable"]', + root: '.wizard-form__content [data-testid="parameters.parametersTable"]', header: {}, body: { - root: '.form-table__header-row', + root: '.form-table__header-row', row: { root: '.form-table__cell', fields: { @@ -259,19 +257,18 @@ const parametersHeaders = { const podsPriorityDropdown = dropdownComponent( generateDropdownGroup( '.modal__content .modal__body .job-wizard__resources .resources__select', - '.form-field-select .form-field__wrapper-normal', + '.form-field-select .form-field__wrapper-normal', '.options-list__body .select__item-label', '.data-ellipsis' ) ) const resourcesNodeSelectorTable = { - root: - '.wizard-form__content [data-testid="resources.nodeSelectorTable"]', + root: '.wizard-form__content [data-testid="resources.nodeSelectorTable"]', header: { sorters: { key: '.form-table__cell_1:nth-of-type(1)', - value: '.form-table__cell_1:nth-of-type(2)', + value: '.form-table__cell_1:nth-of-type(2)' } }, body: { @@ -290,12 +287,7 @@ const resourcesNodeSelectorTable = { }, value_input: { componentType: inputGroup, - structure: generateInputGroup( - '.form-table__cell_1:nth-of-type(2)', - true, - false, - false - ) + structure: generateInputGroup('.form-table__cell_1:nth-of-type(2)', true, false, false) } } } @@ -311,14 +303,13 @@ const actionMenuStructure = { } const volumePathsTable = { - root: - '.modal__content .form-row:nth-of-type(7)', + root: '.modal__content .form-row:nth-of-type(7)', header: { root: '.form-table__header-row', sorters: { type: '.form-table__cell:nth-of-type(1)', volume_name: '.form-table__cell:nth-of-type(2)', - path: '.form-table__cell:nth-of-type(3)', + path: '.form-table__cell:nth-of-type(3)' } }, body: { @@ -362,10 +353,8 @@ const dataInputsInferenceTable = { ) } } - } } - } const dataInputsTable = { root: '.wizard-form__content [data-testid="dataInputs.dataInputsTable"]', @@ -384,50 +373,55 @@ const dataInputsTable = { path_dropdown: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1:nth-of-type(3) .form-field-combobox', - '.form-field__icons:nth-of-type(1)', - '.form-field-combobox__dropdown-list-option', - false, - false) + '.form-table__cell_1:nth-of-type(3) .form-field-combobox', + '.form-field__icons:nth-of-type(1)', + '.form-field-combobox__dropdown-list-option', + false, + false + ) }, path_dropdown_autocomplete_artifacts: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1:nth-of-type(3) .form-field-combobox', - '.form-field-combobox__input', - '.form-field-combobox__dropdown-list-option', - false, - false) + '.form-table__cell_1:nth-of-type(3) .form-field-combobox', + '.form-field-combobox__input', + '.form-field-combobox__dropdown-list-option', + false, + false + ) }, path_dropdown_autocomplete_project: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1:nth-of-type(3) .form-field-combobox', - '.form-field-combobox__input', - '.form-field-combobox__dropdown-list-option', - false, - false) + '.form-table__cell_1:nth-of-type(3) .form-field-combobox', + '.form-field-combobox__input', + '.form-field-combobox__dropdown-list-option', + false, + false + ) }, path_dropdown_autocomplete_item: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1:nth-of-type(3) .form-field-combobox', - '.form-field-combobox__input', - '.form-field-combobox__dropdown-list-option', - false, - false) + '.form-table__cell_1:nth-of-type(3) .form-field-combobox', + '.form-field-combobox__input', + '.form-field-combobox__dropdown-list-option', + false, + false + ) }, path_dropdown_autocomplete_tag: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1:nth-of-type(3) .form-field-combobox', - '.form-field-combobox__input', - '.form-field-combobox__dropdown-list-option', - false, - false) + '.form-table__cell_1:nth-of-type(3) .form-field-combobox', + '.form-field-combobox__input', + '.form-field-combobox__dropdown-list-option', + false, + false + ) }, path_input: 'input.form-field-combobox__input', - path_verify: '.form-table__cell_1:nth-of-type(3)' + path_verify: '.form-table__cell_1:nth-of-type(3)' } } } @@ -449,15 +443,16 @@ const parametersTable = { type_dropdown: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1 .form-field-select', - '.form-field__icons', - '.pop-up-dialog .options-list__body .select__item', - false, - false) + '.form-table__cell_1 .form-field-select', + '.form-field__icons', + '.pop-up-dialog .options-list__body .select__item', + false, + false + ) }, - type_dropdown_verify: '.form-table__cell_1 .data-ellipsis', + type_dropdown_verify: '.form-table__cell_1 .data-ellipsis', value_input: '.form-table__cell_3 .form-field__control input', - value_verify: '.form-table__cell_3 .data-ellipsis' + value_verify: '.form-table__cell_3 .data-ellipsis' } } } @@ -481,16 +476,18 @@ const advancedEnvironmentVariablesTable = { type_dropdown: { componentType: dropdownComponent, structure: generateDropdownGroup( - '.form-table__cell_1 .form-field-select', - '.form-field__icons', - '.pop-up-dialog .options-list__body .select__item', - false, - false) + '.form-table__cell_1 .form-field-select', + '.form-field__icons', + '.pop-up-dialog .options-list__body .select__item', + false, + false + ) }, - type_dropdown_verify: '.form-table__cell_1 .data-ellipsis', + type_dropdown_verify: '.form-table__cell_1 .data-ellipsis', value_input: '.form-table__cell_3 .form-field__control input', value_verify: '.form-table__cell_3 .data-ellipsis', - value_input_key: '.form-table__cell_3 .form-field-input:nth-of-type(2) .form-field__control input' + value_input_key: + '.form-table__cell_3 .form-field-input:nth-of-type(2) .form-field__control input' } } } @@ -503,12 +500,9 @@ const functionsTableSelector = { row: { root: '.job-card-template', fields: { - name: - '.job-card-template__header > div:first-child', - sub_name: - '.job-card-template__header .job-card-template__sub-header .data-ellipsis', - description: - '.job-card-template__description', + name: '.job-card-template__header > div:first-child', + sub_name: '.job-card-template__header .job-card-template__sub-header .data-ellipsis', + description: '.job-card-template__description', labels: { componentType: dropdownComponent, structure: generateDropdownGroup( @@ -571,8 +565,7 @@ const categorySelect = dropdownComponent( ) const runDetailsLabelsTable = { - root: - '.job-wizard__run-details .form-row:nth-of-type(4) .chips', + root: '.job-wizard__run-details .form-row:nth-of-type(4) .chips', header: {}, body: { root: '.chips-wrapper', @@ -597,10 +590,8 @@ const checkboxCategorySelector = { row: { root: '.category', fields: { - name: - '.form-field-checkbox label', - checkbox: - '.form-field-checkbox input' + name: '.form-field-checkbox label', + checkbox: '.form-field-checkbox input' } } } @@ -649,21 +640,11 @@ const commonScheduleButton = By.css('.modal__content [data-testid="schedule-btn" const commonRunSaveButton = By.css('.modal__content [data-testid="run-btn"]') const commonLabelFilterInput = inputGroup( - generateInputGroup( - '[data-testid="labels-form-field-input"]', - true, - false, - true - ) + generateInputGroup('[data-testid="labels-form-field-input"]', true, false, true) ) const commonProjectFilterInput = inputGroup( - generateInputGroup( - '[data-testid="project-form-field-input"]', - true, - false, - true - ) + generateInputGroup('[data-testid="project-form-field-input"]', true, false, true) ) const commonTableTreeFilterDropdown = dropdownComponent( @@ -693,8 +674,12 @@ module.exports = { Create_Button: By.css('.pop-up-dialog .btn-secondary'), Error_Message: By.css('.pop-up-dialog .error__message'), New_Project_Labels_Table: commonTable(newProjectLabelsTable), - Add_Label_Button: By.css('.create-project-dialog .form-row:nth-of-type(4) .chips .chips-wrapper button'), - Close_Label_Button: By.css('.create-project-dialog .form-row:nth-of-type(4) .chips .chips-wrapper .item-icon-close'), + Add_Label_Button: By.css( + '.create-project-dialog .form-row:nth-of-type(4) .chips .chips-wrapper button' + ), + Close_Label_Button: By.css( + '.create-project-dialog .form-row:nth-of-type(4) .chips .chips-wrapper .item-icon-close' + ), Labels_Key: inputGroup( generateInputGroup( '.create-project-dialog .form-row:nth-of-type(4) .chips .chips-wrapper', @@ -711,7 +696,7 @@ module.exports = { false, '.input-label-value' ) - ), + ) }, commonPopup: { Title: commonTitle, @@ -723,12 +708,8 @@ module.exports = { Message: By.css('#overlay_container > div > div > div:nth-child(2)') }, metricsSelectorPopup: { - Search_Metrics_Input:inputGroup( - generateInputGroup( - '[data-testid="metricSearchName-form-field-input"]', - false, - false - ) + Search_Metrics_Input: inputGroup( + generateInputGroup('[data-testid="metricSearchName-form-field-input"]', false, false) ), Evidently_App_Test_Accordion: { Accordion_Header: By.css( @@ -762,15 +743,14 @@ module.exports = { Cross_Close_Button: By.css('.modal .modal__header-button'), Preview_text: By.css('.modal .modal__content .modal__header-preview-text'), Wizard_Steps_Content: commonTable(wizardStepsContent), - Function_Title: By.css( - '.modal .modal__content h6.modal__header-sub-title' - ), + Function_Title: By.css('.modal .modal__content h6.modal__header-sub-title'), Function_Selection_Tabs: commonTable(functionSelectionTabs), Search_Input: inputWithAutocomplete({ root: '.form-row .search-container', elements: { input: 'input', - options: '.functions-list > div > div.job-card-template__header > div.data-ellipsis.tooltip-wrapper', + options: + '.functions-list > div > div.job-card-template__header > div.data-ellipsis.tooltip-wrapper', option_name: '' } }), @@ -784,11 +764,13 @@ module.exports = { }, Category_Selector_Dropdown: categorySelect, Checkbox_Category_Selector: commonTable(checkboxCategorySelector), - Overlay: By.css('#overlay_container .chip-block-hidden .chip-block-hidden__scrollable-container'), + Overlay: By.css( + '#overlay_container .chip-block-hidden .chip-block-hidden__scrollable-container' + ), Hyperparameter_Checkbox: checkboxComponent({ root: '#overlay_container .form-field-checkbox', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -810,13 +792,25 @@ module.exports = { '.form-field__icons svg' ) ), - Version_Tag_Dropdown: By.css('[data-testid="runDetails.version-form-field-select"] [data-testid="select-header"]'), + Version_Tag_Dropdown: By.css( + '[data-testid="runDetails.version-form-field-select"] [data-testid="select-header"]' + ), Handler_Dropdown: dropdownComponent( - generateDropdownGroup('.form-col-1:nth-of-type(3)', '[data-testid="runDetails.handler-form-field-select"]', '.select__item-main-label', false, false) + generateDropdownGroup( + '.form-col-1:nth-of-type(3)', + '[data-testid="runDetails.handler-form-field-select"]', + '.select__item-main-label', + false, + false + ) + ), + Handler_Edit_Job: By.css( + '.form-col-1:nth-of-type(2) [data-testid="runDetails.handler-form-input"]' ), - Handler_Edit_Job: By.css('.form-col-1:nth-of-type(2) [data-testid="runDetails.handler-form-input"]'), Labels_Table: commonTable(labelsTable), - Add_Label_Button: By.css('.job-wizard__run-details .form-row:nth-of-type(4) .chips .chips-wrapper .button-add'), + Add_Label_Button: By.css( + '.job-wizard__run-details .form-row:nth-of-type(4) .chips .chips-wrapper .button-add' + ), Run_Details_Labels_Key: inputGroup( generateInputGroup( '.job-wizard__run-details .form-row:nth-of-type(4) .chips-wrapper', @@ -825,8 +819,12 @@ module.exports = { '.pop-up-dialog' ) ), - Run_Details_Labels_Value: By.css ('.job-wizard__run-details .form-row:nth-of-type(4) .chips-wrapper [id="runDetails.labels[0].value"]'), - Close_Label_Button: By.css('.job-wizard__run-details .form-row:nth-of-type(4) .chips .chips-wrapper .item-icon-close'), + Run_Details_Labels_Value: By.css( + '.job-wizard__run-details .form-row:nth-of-type(4) .chips-wrapper [id="runDetails.labels[0].value"]' + ), + Close_Label_Button: By.css( + '.job-wizard__run-details .form-row:nth-of-type(4) .chips .chips-wrapper .item-icon-close' + ), Image_Name_Input_Run_Details: inputGroup( generateInputGroup( '.job-wizard__run-details > div.form-field-input .form-field__wrapper', @@ -839,12 +837,26 @@ module.exports = { Data_Inputs_Headers: commonTable(dataInputsHeaders), Run_Name_Field: By.css('.form-row .form-field-input .form-field__wrapper input'), Version_Dropdown: dropdownComponent( - generateDropdownGroup('.form-col-1:nth-of-type(2)', '.form-field-select', '.form-field__select-value', false, false) + generateDropdownGroup( + '.form-col-1:nth-of-type(2)', + '.form-field-select', + '.form-field__select-value', + false, + false + ) ), Method_Dropdown: dropdownComponent( - generateDropdownGroup('.form-col-1:nth-of-type(3)', '.form-field-select', '.select__item-main-label', false, false) + generateDropdownGroup( + '.form-col-1:nth-of-type(3)', + '.form-field-select', + '.select__item-main-label', + false, + false + ) + ), + Method_Dropdown_Option: By.css( + '.form-col-1:nth-of-type(3) .form-field-select .form-field__select span' ), - Method_Dropdown_Option: By.css('.form-col-1:nth-of-type(3) .form-field-select .form-field__select span'), Method_Dropdown_Label: By.css('[data-testid="runDetails.handler-form-label"]'), Method_Dropdown_Label_Select: By.css('[data-testid="runDetails.handler-form-select-label"]'), Run_Details_Labels_Table: commonTable(runDetailsLabelsTable), @@ -852,36 +864,39 @@ module.exports = { Data_Inputs_Inference_Table: commonTable(dataInputsInferenceTable), Parameters_Headers: commonTable(parametersHeaders), Parameters_Table: commonTable(parametersTable), - Add_Custom_Parameter_Button: By.css('.job-wizard__parameters [data-testid="parameters.parametersTable"] .form-table__action-row button'), + Add_Custom_Parameter_Button: By.css( + '.job-wizard__parameters [data-testid="parameters.parametersTable"] .form-table__action-row button' + ), Checkbox_Parameters: checkboxComponent( - generateCheckboxGroup('.job-wizard__parameters .form-table__row_active .form-field-checkbox input', false, false, false) - ), - Delete_Button_Parameters: By.css('.job-wizard__parameters [data-testid="delete-discard-btn-tooltip-wrapper"]'), - Parameters_Accordion:{ - Parameters_From_UI_Radiobutton: radiobuttonComponent( - { - root: - '.modal__content .wizard-form__content-container .form-row .form-field-radio:nth-of-type(1)', - elements: { - radiobutton: 'input', - mark: 'label', - name: '', - description: '' - } + generateCheckboxGroup( + '.job-wizard__parameters .form-table__row_active .form-field-checkbox input', + false, + false, + false + ) + ), + Delete_Button_Parameters: By.css( + '.job-wizard__parameters [data-testid="delete-discard-btn-tooltip-wrapper"]' + ), + Parameters_Accordion: { + Parameters_From_UI_Radiobutton: radiobuttonComponent({ + root: '.modal__content .wizard-form__content-container .form-row .form-field-radio:nth-of-type(1)', + elements: { + radiobutton: 'input', + mark: 'label', + name: '', + description: '' } - ), - Parameters_From_File_Radiobutton: radiobuttonComponent( - { - root: - '.modal__content .wizard-form__content-container .form-row .form-field-radio:nth-of-type(2)', - elements: { - radiobutton: 'input', - mark: '', - name: 'label', - description: '' - } + }), + Parameters_From_File_Radiobutton: radiobuttonComponent({ + root: '.modal__content .wizard-form__content-container .form-row .form-field-radio:nth-of-type(2)', + elements: { + radiobutton: 'input', + mark: '', + name: 'label', + description: '' } - ), + }), Parameters_From_File_Input: inputGroup( generateInputGroup( '.job-wizard__parameters .form-row .form-field-input .form-field__wrapper', @@ -890,11 +905,15 @@ module.exports = { false ) ), - Hyper_Toggle_Switch: By.css('.modal__content .form-table__row:nth-of-type(2) .form-table__cell_hyper .form-field-toggle__switch') + Hyper_Toggle_Switch: By.css( + '.modal__content .form-table__row:nth-of-type(2) .form-table__cell_hyper .form-field-toggle__switch' + ) }, Resources_Accordion: { Pods_Priority_Dropdown: podsPriorityDropdown, - Node_Selection_Subheader: By.css(' .modal__content .wizard-form__content-container .job-wizard__resources .form-row:nth-child(3)'), + Node_Selection_Subheader: By.css( + ' .modal__content .wizard-form__content-container .job-wizard__resources .form-row:nth-child(3)' + ), Volumes_Subheader: labelComponent( generateLabelGroup( '.modal__content .wizard-form__content-container .form-row:nth-child(6)', @@ -975,10 +994,22 @@ module.exports = { true ) ), - Edit_Volume_Name_Input: inputGroup(generateInputGroup('.volumes-table .edit-row:not(.no-border_top) .table__cell-input:nth-of-type(2)')), - Edit_Volume_Path_Input: inputGroup(generateInputGroup('.volumes-table .edit-row:not(.no-border_top) .table__cell-input:nth-of-type(3)')), - Add_New_Row_Button: By.css('[data-testid="resources.volumesTable"] .form-table__actions-cell .round-icon-cp:nth-of-type(1)'), - Delete_New_Row_Button: By.css('[data-testid="resources.volumesTable"] .form-table__actions-cell .round-icon-cp:nth-of-type(2)'), + Edit_Volume_Name_Input: inputGroup( + generateInputGroup( + '.volumes-table .edit-row:not(.no-border_top) .table__cell-input:nth-of-type(2)' + ) + ), + Edit_Volume_Path_Input: inputGroup( + generateInputGroup( + '.volumes-table .edit-row:not(.no-border_top) .table__cell-input:nth-of-type(3)' + ) + ), + Add_New_Row_Button: By.css( + '[data-testid="resources.volumesTable"] .form-table__actions-cell .round-icon-cp:nth-of-type(1)' + ), + Delete_New_Row_Button: By.css( + '[data-testid="resources.volumesTable"] .form-table__actions-cell .round-icon-cp:nth-of-type(2)' + ), Apply_Edit_Button: By.css('.volumes-table .apply-edit-btn'), Volume_Paths_Table: commonTable(volumePathsTable), Memory_Request_Dropdown: dropdownComponent( @@ -1002,12 +1033,12 @@ module.exports = { ) ), Memory_Limit_Dropdown: dropdownComponent( - generateDropdownGroup( - '.wizard-form__content-container .resources-units .form-col-1:nth-of-type(1) .resources-card__fields:nth-child(3)', - '.resources-card__fields-select', - '.options-list__body .select__item', - '.data-ellipsis .data-ellipsis' - ) + generateDropdownGroup( + '.wizard-form__content-container .resources-units .form-col-1:nth-of-type(1) .resources-card__fields:nth-child(3)', + '.resources-card__fields-select', + '.options-list__body .select__item', + '.data-ellipsis .data-ellipsis' + ) ), Memory_Limit_Number_Input: numberInputGroup( generateNumberInputGroup( @@ -1036,12 +1067,12 @@ module.exports = { ) ), CPU_Limit_Dropdown: dropdownComponent( - generateDropdownGroup( - '.wizard-form__content-container .resources-units .form-col-1:nth-of-type(2) .resources-card__fields:nth-child(3)', - '.resources-card__fields-select', - '.options-list__body .select__item', - '.data-ellipsis .data-ellipsis' - ) + generateDropdownGroup( + '.wizard-form__content-container .resources-units .form-col-1:nth-of-type(2) .resources-card__fields:nth-child(3)', + '.resources-card__fields-select', + '.options-list__body .select__item', + '.data-ellipsis .data-ellipsis' + ) ), CPU_Limit_Number_Input: numberInputGroup( generateNumberInputGroup( @@ -1069,12 +1100,7 @@ module.exports = { generateCheckboxGroup('.job-wizard__advanced .access-key-checkbox input', false, false, false) ), Access_Key_Input: inputGroup( - generateInputGroup( - '.align-stretch .form-field-input', - true, - false, - '.tooltip-wrapper svg' - ) + generateInputGroup('.align-stretch .form-field-input', true, false, '.tooltip-wrapper svg') ), Advanced_Accordion: { Default_Input_Path_Input: inputGroup( @@ -1094,11 +1120,11 @@ module.exports = { ) ) }, - Hyperparameter_Strategy_Accordion:{ + Hyperparameter_Strategy_Accordion: { Strategy_Dropdown: dropdownComponent( generateDropdownGroup( '.modal__content .modal__body .job-wizard__hyperparameter-strategy .strategy-grid-item', - '.form-field-select .form-field__wrapper-normal', + '.form-field-select .form-field__wrapper-normal', '.options-list__body .select__item-label', '.data-ellipsis' ) @@ -1131,14 +1157,16 @@ module.exports = { ), Ranking_Criteria_Dropdown: dropdownComponent( generateDropdownGroup( - '.job-wizard__hyperparameter-strategy .criteria-grid-item', - '[data-testid="hyperparameterStrategy.criteria-form-field-select"]', - '.options-list .select__item', - false, + '.job-wizard__hyperparameter-strategy .criteria-grid-item', + '[data-testid="hyperparameterStrategy.criteria-form-field-select"]', + '.options-list .select__item', + false, false ) ), - Stop_Condition_Subheader: By.css('.job-wizard__hyperparameter-strategy .stop-condition-title-grid-item'), + Stop_Condition_Subheader: By.css( + '.job-wizard__hyperparameter-strategy .stop-condition-title-grid-item' + ), Stop_Condition_Input: inputGroup( generateInputGroup( '.job-wizard__hyperparameter-strategy .stop-condition-grid-item .form-field__control', @@ -1147,7 +1175,9 @@ module.exports = { false ) ), - Parallelism_Subheader: By.css('.job-wizard__hyperparameter-strategy .parallelism-title-grid-item'), + Parallelism_Subheader: By.css( + '.job-wizard__hyperparameter-strategy .parallelism-title-grid-item' + ), Parallel_Runs_Number_Input: numberInputGroup( generateNumberInputGroup( '.job-wizard__hyperparameter-strategy .parallel-runs-grid-item .form-field-input', @@ -1168,12 +1198,15 @@ module.exports = { Teardown_Checkbox: checkboxComponent({ root: '.job-wizard__hyperparameter-strategy .teardown-dask-grid-item .form-field-checkbox', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } }) }, + mLRunUnhealthyPopUp: { + Message: By.css('[data-testid="pop-up-dialog"]') + }, registerDataset: { Title: commonPopupTitle, Form_Text: commonFormText, @@ -1182,16 +1215,14 @@ module.exports = { Name_Input: inputGroup(commonNameInput), Tag_Input: inputGroup(commonTagInput), Target_Path: { - Path_Scheme_Combobox: comboBox( - '.form .form-row:nth-of-type(4) .form-field__wrapper', - true - ) + Path_Scheme_Combobox: comboBox('.form .form-row:nth-of-type(4) .form-field__wrapper', true) }, Description_Input: textAreaGroup( generateTextAreaGroup( - '.form .form-row:nth-of-type(3) .form-field-textarea', - '.form-field__counter' - )), + '.form .form-row:nth-of-type(3) .form-field-textarea', + '.form-field__counter' + ) + ), Cancel_Button: commonFormCancelButton, Register_Button: commonFormConfirmButton, Register_Error_Message: commonRegisterErrorMessage @@ -1231,10 +1262,7 @@ module.exports = { Tag_Input: inputGroup(commonTagInput), Register_Error_Message: commonRegisterErrorMessage, Target_Path: { - Path_Scheme_Combobox: comboBox( - '.form .form-row:nth-of-type(4) .form-field__wrapper', - true - ) + Path_Scheme_Combobox: comboBox('.form .form-row:nth-of-type(4) .form-field__wrapper', true) }, New_File_Description_Input: textAreaGroup(commonDescriptionTextArea), New_File_Type_Dropdown: dropdownComponent( @@ -1259,10 +1287,7 @@ module.exports = { ) ), Target_Path: { - Path_Scheme_Combobox: comboBox( - '.form .form-row:nth-of-type(3) .form-field__wrapper', - true - ) + Path_Scheme_Combobox: comboBox('.form .form-row:nth-of-type(3) .form-field__wrapper', true) }, New_File_Description_Input: textAreaGroup( generateTextAreaGroup( @@ -1348,7 +1373,7 @@ module.exports = { Delete_Button: By.css('.confirm-dialog .btn-danger'), Overwrite_Button: By.css('.confirm-dialog .btn-primary') }, - previewPopup:{ + previewPopup: { Title: By.css('.pop-up-dialog .pop-up-dialog__header'), Cross_Cancel_Button: commonCrossCancelButton, Preview_Modal_Container: By.css('.pop-up-dialog .item-artifacts__modal-preview'), @@ -1445,10 +1470,20 @@ module.exports = { Title: commonTitle, Cross_Cancel_Button: commonCrossCancelButton, New_Secret_Key_Input: inputGroup( - generateInputGroup('[data-testid="secrets"] .form-table__row_active .form-table__cell_1:nth-of-type(1)', true, false, '.form-field__warning svg') + generateInputGroup( + '[data-testid="secrets"] .form-table__row_active .form-table__cell_1:nth-of-type(1)', + true, + false, + '.form-field__warning svg' + ) ), New_Secret_Value_Input: inputGroup( - generateInputGroup('[data-testid="secrets"] .form-table__row_active .form-table__cell_1:nth-of-type(2)', true, false, '.form-field__warning svg') + generateInputGroup( + '[data-testid="secrets"] .form-table__row_active .form-table__cell_1:nth-of-type(2)', + true, + false, + '.form-field__warning svg' + ) ), Cancel_Button: By.css('.pop-up-dialog .btn-label'), Save_Button: By.css('.pop-up-dialog .secrets__footer-container .btn.btn-primary') @@ -1473,12 +1508,27 @@ module.exports = { Title: commonTitle, Cross_Cancel_Button: commonCrossCancelButton, Name_Input: inputGroup( - generateInputGroup('.pop-up-dialog [data-testid="name-form-field-input"] .form-field__wrapper', true, '.form-field__warning svg', true) + generateInputGroup( + '.pop-up-dialog [data-testid="name-form-field-input"] .form-field__wrapper', + true, + '.form-field__warning svg', + true + ) ), Tag_Input: inputGroup( - generateInputGroup('.pop-up-dialog [data-testid="tag-form-field-input"] .form-field__wrapper', true, '.form-field__warning svg', true) + generateInputGroup( + '.pop-up-dialog [data-testid="tag-form-field-input"] .form-field__wrapper', + true, + '.form-field__warning svg', + true + ) + ), + Description_Input: textAreaGroup( + generateTextAreaGroup( + '.pop-up-dialog .new-feature-vector__description-row', + '.form-field__counter' + ) ), - Description_Input: textAreaGroup(generateTextAreaGroup('.pop-up-dialog .new-feature-vector__description-row', '.form-field__counter')), Labels_Table: commonTable(createFeatureVectorLabelsTable), Cancel_Button: commonCancelButton, Create_Button: commonConfirmButton @@ -1499,7 +1549,9 @@ module.exports = { Cross_Cancel_Button: commonCrossCancelButton, Preview_Row: commonTable(artifactsPreviewRow), Preview_Header: commonTable(artifactsPreviewHeader), - Download_Button: By.css('.pop-up-dialog .preview-body .preview-item:nth-of-type(2) .preview-body__download') + Download_Button: By.css( + '.pop-up-dialog .preview-body .preview-item:nth-of-type(2) .preview-body__download' + ) }, removeMemberPopup: { Title: By.css('.delete-member__pop-up .pop-up-dialog__header-text'), @@ -1513,12 +1565,7 @@ module.exports = { filterByPopup: { Title: By.css('[data-testid="pop-up-dialog"] h3'), Table_Label_Filter_Input: inputGroup( - generateInputGroup( - '[data-testid="labels-form-field-input"]', - true, - false, - 'svg' - ) + generateInputGroup('[data-testid="labels-form-field-input"]', true, false, 'svg') ), Table_Project_Filter_Input: commonProjectFilterInput, Table_Tree_Filter_Dropdown: commonTableTreeFilterDropdown, @@ -1540,7 +1587,7 @@ module.exports = { Show_Iterations_Checkbox: checkboxComponent({ root: '#overlay_container .form-field-checkbox input', elements: { - checkbox: '', + checkbox: '', name: '', icon: '' } @@ -1548,7 +1595,7 @@ module.exports = { Show_Untagged: checkboxComponent({ root: '#overlay_container .form-field-checkbox input', elements: { - checkbox: '', + checkbox: '', name: '', icon: '' } @@ -1556,7 +1603,7 @@ module.exports = { Status_All_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(1)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1564,7 +1611,7 @@ module.exports = { Status_Aborting_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(3)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1572,7 +1619,7 @@ module.exports = { Status_Jobs_Running_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(6)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1580,7 +1627,7 @@ module.exports = { Status_Workflows_Running_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(4)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1588,7 +1635,7 @@ module.exports = { Status_Pending_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(7)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1596,7 +1643,7 @@ module.exports = { Status_Aborted_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(2)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1604,7 +1651,7 @@ module.exports = { Status_Jobs_Error_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(5)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1612,7 +1659,7 @@ module.exports = { Status_Workflows_Error_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(2)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1620,7 +1667,7 @@ module.exports = { Status_Failed_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(3)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1628,7 +1675,7 @@ module.exports = { Status_Jobs_Completed_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(4)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1636,7 +1683,7 @@ module.exports = { Status_Workflows_Completed_Checkbox: checkboxComponent({ root: '[data-testid="select-checkbox"]:nth-of-type(5)', elements: { - checkbox: 'input', + checkbox: 'input', name: 'label', icon: '' } @@ -1647,7 +1694,9 @@ module.exports = { }, downloadsPopUp: { Download_Pop_Up: By.css('[data-testid="download-container"]'), - Download_Pop_Up_Cross_Cancel_Button: By.css('[data-testid="download-container"] .notification__button-close'), + Download_Pop_Up_Cross_Cancel_Button: By.css( + '[data-testid="download-container"] .notification__button-close' + ), Header_Download_Pop_Up: By.css('[data-testid="download-container"] .download-container__header') }, notificationPopUp: { @@ -1659,12 +1708,14 @@ module.exports = { Add_Tag_Popup: By.css('[data-testid="modal"]'), Close_Button: By.css('[data-testid="modal"] .modal__header-button'), Title: By.css('[data-testid="modal"] .modal__content .modal__header h5'), - Input_Label: By.css('[data-testid="modal"] .modal__body [data-testid="artifactTag-form-label"]'), + Input_Label: By.css( + '[data-testid="modal"] .modal__body [data-testid="artifactTag-form-label"]' + ), Tag_Input: inputGroup( generateInputGroup( - '[data-testid="modal"] .modal__body .form-field__wrapper', - true, - '.form-field__warning svg', + '[data-testid="modal"] .modal__body .form-field__wrapper', + true, + '.form-field__warning svg', true ) ), @@ -1673,7 +1724,9 @@ module.exports = { }, deleteArtifactPopup: { Delete_Artifact_Popup: By.css('[data-testid="pop-up-dialog"]'), - Close_Button: By.css('[data-testid="pop-up-dialog"] .pop-up-dialog__header .pop-up-dialog__btn_close'), + Close_Button: By.css( + '[data-testid="pop-up-dialog"] .pop-up-dialog__header .pop-up-dialog__btn_close' + ), Title: By.css('[data-testid="pop-up-dialog"] .pop-up-dialog__header .tooltip-wrapper span'), Dialog_Message: By.css('[data-testid="pop-up-dialog"] .confirm-dialog__message'), Delete_Data_Checkbox: checkboxComponent( @@ -1684,8 +1737,12 @@ module.exports = { false ) ), - Delete_Button: By.css('[data-testid="pop-up-dialog"] .confirm-dialog__btn-container .btn-danger'), - Cancel_Button: By.css('[data-testid="pop-up-dialog"] .confirm-dialog__btn-container .pop-up-dialog__btn_cancel') + Delete_Button: By.css( + '[data-testid="pop-up-dialog"] .confirm-dialog__btn-container .btn-danger' + ), + Cancel_Button: By.css( + '[data-testid="pop-up-dialog"] .confirm-dialog__btn-container .pop-up-dialog__btn_cancel' + ) }, schedulePopUp: { Schedule_For_Later: { @@ -1714,13 +1771,27 @@ module.exports = { false ) ), - Schedule_item_Sunday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(1)'), - Schedule_item_Monday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(2)'), - Schedule_item_Tuesday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(3)'), - Schedule_item_Wednesday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(4)'), - Schedule_item_Thursday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(5)'), - Schedule_item_Friday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(6)'), - Schedule_item_Saturday: By.css('.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(7)'), + Schedule_item_Sunday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(1)' + ), + Schedule_item_Monday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(2)' + ), + Schedule_item_Tuesday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(3)' + ), + Schedule_item_Wednesday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(4)' + ), + Schedule_item_Thursday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(5)' + ), + Schedule_item_Friday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(6)' + ), + Schedule_item_Saturday: By.css( + '.simple-schedule-item .schedule-repeat-week .schedule-repeat-week_day:nth-of-type(7)' + ), Error_Message: By.css('.schedule-content .error') } } diff --git a/tests/features/common/page-objects/projects.po.js b/tests/features/common/page-objects/projects.po.js index 18172bf2f..a36f41cc9 100644 --- a/tests/features/common/page-objects/projects.po.js +++ b/tests/features/common/page-objects/projects.po.js @@ -36,8 +36,7 @@ const ProjectsTableSelector = { row: { root: '.project-card', fields: { - name: - '.project-card__general-info .project-card__header .project-card__header-title .data-ellipsis.tooltip-wrapper.project-card__title', + name: '.project-card__general-info .project-card__header .project-card__header-title .data-ellipsis.tooltip-wrapper.project-card__title', description: '.project-card__general-info .project-card__content .project-card__description .data-ellipsis', running: { @@ -103,12 +102,11 @@ const ProjectsTableSelector = { } module.exports = { + Retrieving_projects_message: By.css('[data-testid=no-data]'), New_Project_Button: By.css( '.projects__wrapper .projects-content-header-item .page-actions-container .btn_register' ), - Refresh_Projects_Button: By.css( - '.projects-content-header .data-ellipsis button' - ), + Refresh_Projects_Button: By.css('.projects-content-header .data-ellipsis button'), Projects_Table: commonTable(ProjectsTableSelector), Overlay: By.css('#overlay_container .chip-block-hidden_visible'), Active_Projects_Button: By.css( @@ -117,9 +115,7 @@ module.exports = { Archive_Projects_Button: By.css( '.projects__wrapper .projects-content-header .projects-content-header-item .content-menu .content-menu__list li[data-testid=archived] a' ), - Projects_Sorter: By.css( - '.projects-content-header-item .sort .split-btn__button:nth-of-type(1)' - ), + Projects_Sorter: By.css('.projects-content-header-item .sort .split-btn__button:nth-of-type(1)'), Projects_Sort_Dropdown: dropdownComponent( generateDropdownGroup( '.projects-content-header-item .sort .split-btn__button:nth-of-type(2)', @@ -128,63 +124,136 @@ module.exports = { '.data-ellipsis > .tooltip-wrapper' // Option value ) ), - No_Archived_Projects_Label: - '.projects .projects__wrapper .projects-content .no-filtered-data', + No_Archived_Projects_Label: '.projects .projects__wrapper .projects-content .no-filtered-data', Search_Projects_Input: inputGroup( - generateInputGroup( - '.projects__wrapper .projects-content-header .search-container' - ) + generateInputGroup('.projects__wrapper .projects-content-header .search-container') ), - Projects_Monitoring_Container:{ + Projects_Monitoring_Container: { Monitoring_Container: By.css('.projects .projects-monitoring-container'), - Monitoring_Container_Title: By.css('.projects-monitoring-container .projects-monitoring-legend .page-header__title'), - Monitoring_Container_Running_Status: By.css('.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(1)'), - Monitoring_Container_Running_Icon: By.css('.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(1) i'), - Monitoring_Container_Failed_Status: By.css('.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(2)'), - Monitoring_Container_Failed_Icon: By.css('.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(2) i'), - Monitoring_Container_Completed_Status: By.css('.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(3)'), - Monitoring_Container_Completed_Icon: By.css('.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(3) i'), - Monitoring_Jobs_Box: By.css('.projects-monitoring-container .projects-monitoring-stats .stats-card:nth-of-type(1)'), - Monitoring_Workflows_Box: By.css('.projects-monitoring-container .projects-monitoring-stats .stats-card:nth-of-type(2)'), - Monitoring_Scheduled_Box: By.css('.projects-monitoring-container .projects-monitoring-stats .stats-card:nth-of-type(3)') + Monitoring_Container_Title: By.css( + '.projects-monitoring-container .projects-monitoring-legend .page-header__title' + ), + Monitoring_Container_Running_Status: By.css( + '.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(1)' + ), + Monitoring_Container_Running_Icon: By.css( + '.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(1) i' + ), + Monitoring_Container_Failed_Status: By.css( + '.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(2)' + ), + Monitoring_Container_Failed_Icon: By.css( + '.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(2) i' + ), + Monitoring_Container_Completed_Status: By.css( + '.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(3)' + ), + Monitoring_Container_Completed_Icon: By.css( + '.projects-monitoring-container .projects-monitoring-legend .projects-monitoring-legend__status li:nth-of-type(3) i' + ), + Monitoring_Jobs_Box: By.css( + '.projects-monitoring-container .projects-monitoring-stats .stats-card:nth-of-type(1)' + ), + Monitoring_Workflows_Box: By.css( + '.projects-monitoring-container .projects-monitoring-stats .stats-card:nth-of-type(2)' + ), + Monitoring_Scheduled_Box: By.css( + '.projects-monitoring-container .projects-monitoring-stats .stats-card:nth-of-type(3)' + ) }, - Monitoring_Jobs_Box:{ - Monitoring_Jobs_Box_Title: By.css('.projects-monitoring-stats .stats-card:nth-of-type(1) .stats-card__title'), + Monitoring_Jobs_Box: { + Monitoring_Jobs_Box_Title: By.css( + '.projects-monitoring-stats .stats-card:nth-of-type(1) .stats-card__title' + ), Filtering_Time_Period: By.css('.stats-card:nth-of-type(1) .project-card__info'), - Total_Counter_Title: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .stats__subtitle'), - Total_Counter_Number: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .stats__counter'), - Counter_Running_Status_Number: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1)'), - Counter_Running_Status_Icon: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1) i'), - Counter_Failed_Status_Number: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2)'), - Counter_Failed_Status_Icon: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2) i'), - Counter_Completed_Status_Number: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3)'), - Counter_Completed_Status_Icon: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3) i'), + Total_Counter_Title: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .stats__subtitle' + ), + Total_Counter_Number: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .stats__counter' + ), + Counter_Running_Status_Number: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1)' + ), + Counter_Running_Status_Icon: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1) i' + ), + Counter_Failed_Status_Number: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2)' + ), + Counter_Failed_Status_Icon: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2) i' + ), + Counter_Completed_Status_Number: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3)' + ), + Counter_Completed_Status_Icon: By.css( + '.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3) i' + ), See_All_Link: By.css('.stats-card:nth-of-type(1) .stats-card__row:nth-of-type(3) .link') }, - Monitoring_Workflows_Box:{ - Monitoring_Workflows_Box_Title: By.css('.projects-monitoring-stats .stats-card:nth-of-type(2) .stats-card__title'), + Monitoring_Workflows_Box: { + Monitoring_Workflows_Box_Title: By.css( + '.projects-monitoring-stats .stats-card:nth-of-type(2) .stats-card__title' + ), Filtering_Time_Period: By.css('.stats-card:nth-of-type(2) .project-card__info'), - Total_Counter_Title: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .stats__subtitle'), - Total_Counter_Number: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .stats__counter'), - Counter_Running_Status_Number: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1)'), - Counter_Running_Status_Icon: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1) i'), - Counter_Failed_Status_Number: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2)'), - Counter_Failed_Status_Icon: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2) i'), - Counter_Completed_Status_Number: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3)'), - Counter_Completed_Status_Icon: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3) i'), + Total_Counter_Title: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .stats__subtitle' + ), + Total_Counter_Number: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .stats__counter' + ), + Counter_Running_Status_Number: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1)' + ), + Counter_Running_Status_Icon: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(1) i' + ), + Counter_Failed_Status_Number: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2)' + ), + Counter_Failed_Status_Icon: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(2) i' + ), + Counter_Completed_Status_Number: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3)' + ), + Counter_Completed_Status_Icon: By.css( + '.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(2) .projects-monitoring-legend__status .link:nth-of-type(3) i' + ), See_All_Link: By.css('.stats-card:nth-of-type(2) .stats-card__row:nth-of-type(3) .link') }, - Monitoring_Scheduled_Box:{ - Monitoring_Scheduled_Box_Title: By.css('.projects-monitoring-stats .stats-card:nth-of-type(3) .stats-card__title'), + Monitoring_Scheduled_Box: { + Monitoring_Scheduled_Box_Title: By.css( + '.projects-monitoring-stats .stats-card:nth-of-type(3) .stats-card__title' + ), Filtering_Time_Period: By.css('.stats-card:nth-of-type(3) .project-card__info'), - Total_Job_Counter_Title: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(1) .stats__subtitle'), - Total_Workflows_Counter_Title: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(2) .stats__subtitle'), - Total_Scheduled_Title: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(3) .stats__subtitle'), - Total_Job_Counter_Number: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(1) .stats__counter'), - Total_Workflows_Counter_Number: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(2) .stats__counter'), - Total_Scheduled_Number: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(3) .stats__counter'), - Jobs_See_All_Link: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(3) .stats-card__col:nth-of-type(1) .link'), - Workflows_See_All_Link: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(3) .stats-card__col:nth-of-type(2) .link'), - Total_See_All_Link: By.css('.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(3) .stats-card__col:nth-of-type(3) .link') + Total_Job_Counter_Title: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(1) .stats__subtitle' + ), + Total_Workflows_Counter_Title: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(2) .stats__subtitle' + ), + Total_Scheduled_Title: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(3) .stats__subtitle' + ), + Total_Job_Counter_Number: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(1) .stats__counter' + ), + Total_Workflows_Counter_Number: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(2) .stats__counter' + ), + Total_Scheduled_Number: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(2) .stats-card__col:nth-of-type(3) .stats__counter' + ), + Jobs_See_All_Link: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(3) .stats-card__col:nth-of-type(1) .link' + ), + Workflows_See_All_Link: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(3) .stats-card__col:nth-of-type(2) .link' + ), + Total_See_All_Link: By.css( + '.stats-card:nth-of-type(3) .stats-card__row:nth-of-type(3) .stats-card__col:nth-of-type(3) .link' + ) } } diff --git a/tests/features/projectsPage.feature b/tests/features/projectsPage.feature index d31bc4999..01c559b36 100644 --- a/tests/features/projectsPage.feature +++ b/tests/features/projectsPage.feature @@ -47,7 +47,7 @@ Feature: Projects Page Then type value "" to "Search_Projects_Input" field on "Projects" wizard Then click on "Refresh_Projects_Button" element on "Projects" wizard Then type value "defa" to "Search_Projects_Input" field on "Projects" wizard - Then value in "name" column with "text" in "Projects_Table" on "Projects" wizard should contains "default" + Then value in "name" column with "text" in "Projects_Table" on "Projects" wizard should contains "default" @MLPr @passive @@ -182,7 +182,7 @@ Feature: Projects Page Then click on "Create_Button" element on "Create_New_Project" wizard And set tear-down property "project" created with "automation-test-name" value Then check "automation-test-name" value in "name" column in "Projects_Table" table on "Projects" wizard - + @MLPr @passive @smoke @@ -198,7 +198,7 @@ Feature: Projects Page Then check "automation-test-name1" value not in "name" column in "Projects_Table" table on "Projects" wizard Then click on "Archive_Projects_Button" element on "Projects" wizard Then check "automation-test-name1" value in "name" column in "Projects_Table" table on "Projects" wizard - + @MLPr @passive @smoke @@ -226,7 +226,7 @@ Feature: Projects Page Then check "automation-test-name2" value not in "name" column in "Projects_Table" table on "Projects" wizard Then verify "New_Project_Button" element visibility on "Projects" wizard Then "New_Project_Button" element on "Projects" should contains "New Project" value - + @MLPr @passive @smoke @@ -252,7 +252,7 @@ Feature: Projects Page Then select "Unarchive" option in action menu on "Projects" wizard in "Projects_Table" table at row with "automation-test-name7" value in "name" column Then click on "Active_Projects_Button" element on "Projects" wizard Then check "automation-test-name7" value in "name" column in "Projects_Table" table on "Projects" wizard - + @MLPr @passive @smoke @@ -267,8 +267,8 @@ Feature: Projects Page Then select "Export YAML" option in action menu on "Projects" wizard in "Projects_Table" table at row with "default" value in "name" column And wait load page Then check that "default.yaml" file is existed on "Downloads" directory - - @MLPr + + @MLPr @danger @smoke # Run this test case only with mocked backend!!! @@ -519,3 +519,27 @@ Feature: Projects Page When click on "Total_See_All_Link" element in "Monitoring_Scheduled_Box" on "Projects" wizard Then verify redirection to "projects/*/jobs-monitoring/scheduled" And wait load page + + @MLPr + @mlrunUnhealthyTest + @passive + @uniqueTag + @smoke + Scenario: MLPr019 - Check Mlrun unhealthy popup + Given open url + And wait load page + Then verify "New_Project_Button" element visibility on "Projects" wizard + Then "New_Project_Button" element on "Projects" should contains "New Project" value + Then verify "Active_Projects_Button" element visibility on "Projects" wizard + Then verify "Archive_Projects_Button" element visibility on "Projects" wizard + Then verify "Projects_Sort_Dropdown" element visibility on "Projects" wizard + Then verify "Projects_Sorter" element visibility on "Projects" wizard + Then verify "Retrieving_projects_message" element visibility on "Projects" wizard + Then "Retrieving_projects_message" element on "Projects" should contains "Retrieving projects." value + And wait load page + Then verify "Notification_Pop_Up" element visibility on "Notification_Popup" wizard + Then "Notification_Pop_Up" element on "Notification_Popup" should contains "Failed to fetch projects" value + Then wait for 181 seconds + Then verify "Message" element visibility on "MLRun_Unhealthy_PopUp" wizard + Then "Message" element on "MLRun_Unhealthy_PopUp" should contains "MLRun seems to be down. Try again in a few minutes." value + And wait load page diff --git a/tests/features/support/hooks.js b/tests/features/support/hooks.js index d7b50dc1e..3ab97927f 100644 --- a/tests/features/support/hooks.js +++ b/tests/features/support/hooks.js @@ -20,15 +20,15 @@ such restriction. import { Before, After, Status } from '@cucumber/cucumber' import wd from 'selenium-webdriver' import { browser } from '../../config' -import { clearBackendAfterTest } from '../common-tools/common-tools' +import { clearBackendAfterTest, setRequestsFailureCondition } from '../common-tools/common-tools' -Before(async function() { +Before(async function () { await this.driver.manage().window() this.createdItems = [] this.testContext = {} }) -After(async function(testCase) { +After(async function (testCase) { if (testCase.result.status === Status.FAILED) { var stream = await this.driver.takeScreenshot() await this.attach(stream, 'base64:image/png') @@ -36,12 +36,7 @@ After(async function(testCase) { let logs = [] if (browser === 'chrome') { await this.driver - .then(() => - this.driver - .manage() - .logs() - .get(wd.logging.Type.BROWSER) - ) + .then(() => this.driver.manage().logs().get(wd.logging.Type.BROWSER)) .then(result => { logs = result }) @@ -51,9 +46,15 @@ After(async function(testCase) { await this.driver.quit() if (logs.some(log => log.level.name_ === 'SEVERE')) { - await logs.forEach(log => - this.attach(`${log.level.name} ${log.message}`, 'text/plain') - ) + await logs.forEach(log => this.attach(`${log.level.name} ${log.message}`, 'text/plain')) // throw new Error('There are some errors in console') } }) + +Before('@mlrunUnhealthyTest', async function () { + await setRequestsFailureCondition(true) +}) + +After('@mlrunUnhealthyTest', async function () { + await setRequestsFailureCondition(false) +}) diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index c4cf5d2f4..7d90161a2 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -92,6 +92,19 @@ const app = express() app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()) +// intercepts all the requests and reject them if `failAllRequests` is true +// should be used ONLY for test framework +let failAllRequests = false +app.use((req, res, next) => { + if (failAllRequests && req.url !== '/set-failure-condition') { + res.statusCode = 502 + + res.send({}) + } else { + next() + } +}) + // MLRun object Templates const projectBackgroundTasks = {} const backgroundTasks = {} @@ -2502,6 +2515,13 @@ function getIguazioJob(req, res) { }) } +// Helper request for AQA framework to fail all the requests +app.post('/set-failure-condition', (req, res) => { + failAllRequests = req.body.shouldFail + + res.send(`Failure condition set to ${failAllRequests}`) +}) + // REQUESTS app.get(`${mlrunAPIIngress}/frontend-spec`, getFrontendSpec) app.get(`${mlrunAPIIngress}/projects/:project/background-tasks/:taskId`, getProjectTask) From c8c3d6c3a337f308dc53328313bfb3eee0ad3606 Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:34:25 +0200 Subject: [PATCH 11/26] Fix [Monitor Jobs] results columns are too wide when result it long (#2851) --- .../DetailsResults/DetailsResults.js | 95 +++++++++++-------- src/utils/resultsTable.js | 2 + 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/components/DetailsResults/DetailsResults.js b/src/components/DetailsResults/DetailsResults.js index 88cf1f132..bc277d63c 100644 --- a/src/components/DetailsResults/DetailsResults.js +++ b/src/components/DetailsResults/DetailsResults.js @@ -52,36 +52,39 @@ const DetailsResults = ({ sortConfig: { allowSortBy, excludeSortBy, defaultSortBy, defaultDirection } }) - const getHeaderCellClasses = (headerId, isSortable) => + const getHeaderCellClasses = (headerId, isSortable, customClassName = '') => classNames( 'table-header__cell', isSortable && 'sortable-header-cell', - isSortable && selectedColumnName === headerId && 'sortable-header-cell_active' + isSortable && selectedColumnName === headerId && 'sortable-header-cell_active', + customClassName ) const getHeaderTemplate = () => { return ( - - - {sortedTableHeaders.map(({ headerLabel, headerId, isSortable, ...tableItem }) => { - return ( - sortTable(headerId) : null} - > - }> - - - {tableItem.tip && } - - ) - })} - - +
+
+ {sortedTableHeaders.map( + ({ headerLabel, headerId, isSortable, className, ...tableItem }) => { + return ( +
sortTable(headerId) : null} + > + }> + + + {tableItem.tip && } +
+ ) + } + )} +
+
) } @@ -90,13 +93,13 @@ const DetailsResults = ({ ) : (
- +
{job.iterationStats && job.iterationStats.length !== 0 ? ( <> {getHeaderTemplate()} -
+
{sortedTableContent.map((rowData, rowIndex) => ( -
+
{rowData.map((cellData, cellIndex) => { if ( job.results && @@ -104,9 +107,13 @@ const DetailsResults = ({ cellIndex === 0 ) { return ( -
+ ) } else { return ( - + ) } })} - + ))} - + ) : job.iterations?.length === 0 && Object.keys(job.results ?? {}).length !== 0 ? ( <> {getHeaderTemplate()} - +
{sortedTableContent.map((rowData, rowIndex) => ( -
+
{rowData.map((cellData, cellIndex) => ( -
+ ))} - + ))} - + ) : null} -
{cellData.value} - +
} > {roundFloats(cellData.value, 4)} -
+
} + template={} > {cellData.value} -
+
) } diff --git a/src/utils/resultsTable.js b/src/utils/resultsTable.js index d0642ca2a..1a6832147 100644 --- a/src/utils/resultsTable.js +++ b/src/utils/resultsTable.js @@ -46,11 +46,13 @@ export const generateResultsContent = job => { { headerId: 'name', headerLabel: 'Name', + className: 'table-cell-3', value: resultName }, { headerId: 'value', headerLabel: 'Value', + className: 'table-cell-8', value: String(resultValue ?? '') } ] From c2bf31c31f4c15a3b7efef990da29b93167c2e58 Mon Sep 17 00:00:00 2001 From: mariana-furyk <58301139+mariana-furyk@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:23:01 +0200 Subject: [PATCH 12/26] Impl [Feature Sets] create a request for each feature set (#2860) --- src/actions/featureStore.js | 37 +++++++-- src/api/featureStore-api.js | 25 ++++-- .../FeatureStore/FeatureSets/FeatureSets.js | 76 +++++++++++++------ .../FeatureSets/FeatureSetsView.js | 8 +- .../FeatureSets/featureSets.util.js | 38 +++++++++- src/constants.js | 11 ++- src/reducers/featureStoreReducer.js | 56 ++++++++++---- tests/mockServer/mock.js | 14 +++- 8 files changed, 208 insertions(+), 57 deletions(-) diff --git a/src/actions/featureStore.js b/src/actions/featureStore.js index 8afa79e37..f306a6a30 100644 --- a/src/actions/featureStore.js +++ b/src/actions/featureStore.js @@ -66,7 +66,9 @@ import { SET_NEW_FEATURE_SET_TARGET, SET_NEW_FEATURE_SET_VERSION, START_FEATURE_SET_INGEST_BEGIN, - START_FEATURE_SET_INGEST_SUCCESS + START_FEATURE_SET_INGEST_SUCCESS, + FETCH_FEATURE_SET_BEGIN, + FETCH_FEATURE_SET_FAILURE } from '../constants' import { CONFLICT_ERROR_STATUS_CODE, FORBIDDEN_ERROR_STATUS_CODE } from 'igz-controls/constants' import { parseFeatureVectors } from '../utils/parseFeatureVectors' @@ -204,14 +206,14 @@ const featureStoreActions = { type: FETCH_FEATURE_SETS_SUCCESS, payload: featureSets }), - fetchFeatureSet: (project, featureSet, tag) => dispatch => { + fetchExpandedFeatureSet: (project, featureSet, tag) => dispatch => { return featureStoreApi - .getFeatureSet(project, featureSet, tag) + .getExpandedFeatureSet(project, featureSet, tag) .then(response => { const generatedFeatureSets = parseFeatureSets(response.data?.feature_sets) dispatch( - featureStoreActions.fetchFeatureSetSuccess({ + featureStoreActions.fetchExpandedFeatureSetSuccess({ [getFeatureSetIdentifier(generatedFeatureSets[0])]: generatedFeatureSets }) ) @@ -222,10 +224,35 @@ const featureStoreActions = { throw error }) }, - fetchFeatureSetSuccess: featureSets => ({ + fetchExpandedFeatureSetSuccess: featureSets => ({ type: FETCH_FEATURE_SET_SUCCESS, payload: featureSets }), + fetchFeatureSet: (project, featureSet, tag) => dispatch => { + dispatch(featureStoreActions.fetchFeatureSetBegin()) + + return featureStoreApi + .getFeatureSet(project, featureSet, tag) + .then(response => { + dispatch(featureStoreActions.fetchFeatureSetSuccess()) + + return parseFeatureSets(response.data?.feature_sets)[0] + }) + .catch(error => { + dispatch(featureStoreActions.fetchFeatureSetFailure(error.message)) + throw error + }) + }, + fetchFeatureSetBegin: () => ({ + type: FETCH_FEATURE_SET_BEGIN + }), + fetchFeatureSetFailure: error => ({ + type: FETCH_FEATURE_SET_FAILURE, + payload: error + }), + fetchFeatureSetSuccess: () => ({ + type: FETCH_FEATURE_SET_SUCCESS + }), fetchFeatureVector: (project, featureVector, tag) => dispatch => { return featureStoreApi .getFeatureVector(project, featureVector, tag) diff --git a/src/api/featureStore-api.js b/src/api/featureStore-api.js index f8e612b14..de103f3ba 100644 --- a/src/api/featureStore-api.js +++ b/src/api/featureStore-api.js @@ -27,7 +27,7 @@ import { } from '../constants' const fetchFeatureStoreContent = (path, filters, config = {}, withLatestTag, apiV2) => { - const params = {} + const params = { ...config.params } const httpClient = apiV2 ? mainHttpClientV2 : mainHttpClient if (filters?.labels) { @@ -66,7 +66,8 @@ const featureStoreApi = { mainHttpClient.post(`/projects/${data.metadata.project}/feature-vectors`, data), deleteFeatureVector: (project, featureVector) => mainHttpClient.delete(`/projects/${project}/feature-vectors/${featureVector}`), - fetchFeatureSetsTags: (project, config) => mainHttpClient.get(`/projects/${project}/feature-sets/*/tags`, config), + fetchFeatureSetsTags: (project, config) => + mainHttpClient.get(`/projects/${project}/feature-sets/*/tags`, config), fetchFeatureVectorsTags: (project, config) => mainHttpClient.get(`/projects/${project}/feature-vectors/*/tags`, config), getEntity: (project, entity) => @@ -75,9 +76,10 @@ const featureStoreApi = { }), getEntities: (project, filters, config) => fetchFeatureStoreContent(`/projects/${project}/entities`, filters, config ?? {}, true, true), - getFeatureSet: (project, featureSet, tag) => { + getExpandedFeatureSet: (project, featureSet, tag) => { const params = { - name: featureSet + name: featureSet, + format: 'minimal' } if (tag !== TAG_FILTER_ALL_ITEMS) { @@ -88,6 +90,13 @@ const featureStoreApi = { params }) }, + getFeatureSet: (project, featureSet, tag) => { + const params = { name: featureSet, tag } + + return mainHttpClient.get(`/projects/${project}/feature-sets`, { + params + }) + }, getFeatureSets: (project, filters, config) => { return fetchFeatureStoreContent( `/projects/${project}/${FEATURE_SETS_TAB}`, @@ -125,7 +134,13 @@ const featureStoreApi = { params: { name: feature } }), getFeatures: (project, filters, config) => - fetchFeatureStoreContent(`/projects/${project}/${FEATURES_TAB}`, filters, config ?? {}, true, true), + fetchFeatureStoreContent( + `/projects/${project}/${FEATURES_TAB}`, + filters, + config ?? {}, + true, + true + ), startIngest: (project, featureSet, reference, data) => mainHttpClient.post( `/projects/${project}/feature-sets/${featureSet}/references/${reference}/ingest`, diff --git a/src/components/FeatureStore/FeatureSets/FeatureSets.js b/src/components/FeatureStore/FeatureSets/FeatureSets.js index 92b631779..14980c3c2 100644 --- a/src/components/FeatureStore/FeatureSets/FeatureSets.js +++ b/src/components/FeatureStore/FeatureSets/FeatureSets.js @@ -37,7 +37,12 @@ import { } from '../../../constants' import { checkTabIsValid, handleApplyDetailsChanges } from '../featureStore.util' import { createFeatureSetsRowData } from '../../../utils/createFeatureStoreContent' -import { featureSetsActionCreator, generatePageData } from './featureSets.util' +import { + chooseOrFetchFeatureSet, + featureSetsActionCreator, + generatePageData, + setFullSelectedFeatureSet +} from './featureSets.util' import { getFeatureSetIdentifier } from '../../../utils/getUniqueIdentifier' import { getFilterTagOptions, setFilters } from '../../../reducers/filtersReducer' import { isDetailsTabExists } from '../../../utils/link-helper.util' @@ -54,7 +59,7 @@ import { ReactComponent as Yaml } from 'igz-controls/images/yaml.svg' import cssVariables from './featureSets.scss' const FeatureSets = ({ - fetchFeatureSet, + fetchExpandedFeatureSet, fetchFeatureSets, fetchFeatureSetsTags, removeFeatureSet, @@ -64,9 +69,9 @@ const FeatureSets = ({ }) => { const [featureSets, setFeatureSets] = useState([]) const [selectedFeatureSet, setSelectedFeatureSet] = useState({}) + const [selectedFeatureSetMin, setSelectedFeatureSetMin] = useState({}) const [selectedRowData, setSelectedRowData] = useState({}) const [requestErrorMessage, setRequestErrorMessage] = useState('') - const openPanelByDefault = useOpenPanel() const params = useParams() const featureStore = useSelector(store => store.featureStore) @@ -98,11 +103,14 @@ const FeatureSets = ({ { label: 'View YAML', icon: , - onClick: toggleConvertedYaml + onClick: featureSetMin => + chooseOrFetchFeatureSet(dispatch, selectedFeatureSet, featureSetMin).then( + toggleConvertedYaml + ) } ] ], - [toggleConvertedYaml] + [dispatch, selectedFeatureSet, toggleConvertedYaml] ) const fetchData = useCallback( @@ -113,6 +121,9 @@ const FeatureSets = ({ ui: { controller: abortControllerRef.current, setRequestErrorMessage + }, + params: { + format: 'minimal' } } @@ -143,13 +154,17 @@ const FeatureSets = ({ ) }, [dispatch, fetchFeatureSetsTags, params.projectName]) - const handleRefresh = useCallback(filters => { - fetchTags() - setFeatureSets([]) - setSelectedFeatureSet({}) - setSelectedRowData({}) - return fetchData(filters) - }, [fetchData, fetchTags]) + const handleRefresh = useCallback( + filters => { + fetchTags() + setFeatureSets([]) + setSelectedFeatureSet({}) + setSelectedRowData({}) + + return fetchData(filters) + }, + [fetchData, fetchTags] + ) const handleRemoveFeatureSet = useCallback( featureSet => { @@ -179,7 +194,7 @@ const FeatureSets = ({ } })) - fetchFeatureSet(item.project, item.name, filtersStore.tag) + fetchExpandedFeatureSet(item.project, item.name, filtersStore.tag) .then(result => { const content = [...parseFeatureSets(result)].map(contentItem => createFeatureSetsRowData(contentItem, params.projectName, FEATURE_SETS_TAB, true) @@ -204,7 +219,7 @@ const FeatureSets = ({ })) }) }, - [fetchFeatureSet, filtersStore.tag, params.projectName] + [fetchExpandedFeatureSet, filtersStore.tag, params.projectName] ) const { latestItems, handleExpandRow } = useGroupContent( @@ -227,11 +242,14 @@ const FeatureSets = ({ ) }, [featureSets, filtersStore.groupBy, latestItems, params.projectName]) - const handleSelectFeatureSet = item => { - if (params.name === item.name && params.tag === item.tag) { - setSelectedFeatureSet(item) - } - } + const handleSelectFeatureSet = useCallback( + item => { + if (params.name === item.name && params.tag === item.tag) { + setSelectedFeatureSetMin(item) + } + }, + [params.name, params.tag] + ) const applyDetailsChanges = useCallback( changes => { @@ -291,6 +309,17 @@ const FeatureSets = ({ removeNewFeatureSet() } + useEffect(() => { + setFullSelectedFeatureSet( + FEATURE_SETS_TAB, + dispatch, + navigate, + selectedFeatureSetMin, + setSelectedFeatureSet, + params.projectName + ) + }, [dispatch, navigate, params.projectName, selectedFeatureSetMin]) + useEffect(() => { setSelectedRowData({}) }, [filtersStore.tag]) @@ -322,17 +351,16 @@ const FeatureSets = ({ (contentItem.tag === params.tag || contentItem.uid === params.tag) ) }) - // console.log(params.projectName) if (!selectedItem) { navigate(`/projects/${params.projectName}/feature-store/${FEATURE_SETS_TAB}`, { replace: true }) } else { - setSelectedFeatureSet(selectedItem) + setSelectedFeatureSetMin(selectedItem) } } else { - setSelectedFeatureSet({}) + setSelectedFeatureSetMin({}) } }, [featureStore.featureSets.allData, navigate, params.name, params.projectName, params.tag]) @@ -359,7 +387,7 @@ const FeatureSets = ({ setFeatureSets([]) removeFeatureSets() removeFeatureSet() - setSelectedFeatureSet({}) + setSelectedFeatureSetMin({}) setSelectedRowData({}) abortControllerRef.current.abort(REQUEST_CANCELED) tagAbortControllerCurrent.abort(REQUEST_CANCELED) @@ -398,7 +426,7 @@ const FeatureSets = ({ requestErrorMessage={requestErrorMessage} selectedFeatureSet={selectedFeatureSet} selectedRowData={selectedRowData} - setSelectedFeatureSet={handleSelectFeatureSet} + setSelectedFeatureSetMin={handleSelectFeatureSet} tableContent={tableContent} virtualizationConfig={virtualizationConfig} /> diff --git a/src/components/FeatureStore/FeatureSets/FeatureSetsView.js b/src/components/FeatureStore/FeatureSets/FeatureSetsView.js index f0855a22a..e93592e56 100644 --- a/src/components/FeatureStore/FeatureSets/FeatureSetsView.js +++ b/src/components/FeatureStore/FeatureSets/FeatureSetsView.js @@ -32,6 +32,7 @@ import { VIRTUALIZATION_CONFIG } from '../../../types' import { featureSetsFilters } from './featureSets.util' import { getNoDataMessage } from '../../../utils/getNoDataMessage' import { isRowRendered } from '../../../hooks/useVirtualization.hook' +import Loader from '../../../common/Loader/Loader' const FeatureSetsView = React.forwardRef( ( @@ -52,7 +53,7 @@ const FeatureSetsView = React.forwardRef( requestErrorMessage, selectedFeatureSet, selectedRowData, - setSelectedFeatureSet, + setSelectedFeatureSetMin, tableContent, virtualizationConfig }, @@ -85,12 +86,13 @@ const FeatureSetsView = React.forwardRef( /> ) : ( <> + {(selectedRowData.loading || featureStore.featureSets.featureSetLoading) && } setSelectedFeatureSet({})} + handleCancel={() => setSelectedFeatureSetMin({})} pageData={pageData} retryRequest={handleRefresh} selectedItem={selectedFeatureSet} @@ -146,7 +148,7 @@ FeatureSetsView.propTypes = { requestErrorMessage: PropTypes.string.isRequired, selectedFeatureSet: PropTypes.object.isRequired, selectedRowData: PropTypes.object.isRequired, - setSelectedFeatureSet: PropTypes.func.isRequired, + setSelectedFeatureSetMin: PropTypes.func.isRequired, tableContent: PropTypes.arrayOf(PropTypes.object).isRequired, virtualizationConfig: VIRTUALIZATION_CONFIG.isRequired } diff --git a/src/components/FeatureStore/FeatureSets/featureSets.util.js b/src/components/FeatureStore/FeatureSets/featureSets.util.js index 360eac159..160aa1d2d 100644 --- a/src/components/FeatureStore/FeatureSets/featureSets.util.js +++ b/src/components/FeatureStore/FeatureSets/featureSets.util.js @@ -25,6 +25,8 @@ import { TAG_FILTER } from '../../../constants' import featureStoreActions from '../../../actions/featureStore' +import { debounce, isEmpty } from 'lodash' +import { showErrorNotification } from '../../../utils/notifications.util' export const generateFeatureSetsDetailsMenu = selectedItem => [ { @@ -87,8 +89,42 @@ export const generatePageData = selectedFeatureSet => { } } +export const setFullSelectedFeatureSet = debounce( + (tab, dispatch, navigate, selectedFeatureSetMin, setSelectedFeatureSet, projectName) => { + if (isEmpty(selectedFeatureSetMin)) { + setSelectedFeatureSet({}) + } else { + const { name, tag } = selectedFeatureSetMin + + dispatch(featureStoreActions.fetchFeatureSet(projectName, name, tag)) + .then(featureSet => { + setSelectedFeatureSet(featureSet) + }) + .catch(error => { + navigate(`/projects/${projectName}/${tab}`, { replace: true }) + showErrorNotification(dispatch, error, '', 'Failed to retrieve feature set data.') + }) + } + }, + 50 +) + +export const chooseOrFetchFeatureSet = (dispatch, selectedFeatureSet, featureSetMin) => { + if (!isEmpty(selectedFeatureSet)) return Promise.resolve(selectedFeatureSet) + + return dispatch( + featureStoreActions.fetchFeatureSet( + featureSetMin.project, + featureSetMin.name, + featureSetMin.tag + ) + ).catch(error => { + showErrorNotification(dispatch, error, 'Failed to retrieve feature set data.') + }) +} + export const featureSetsActionCreator = { - fetchFeatureSet: featureStoreActions.fetchFeatureSet, + fetchExpandedFeatureSet: featureStoreActions.fetchExpandedFeatureSet, fetchFeatureSets: featureStoreActions.fetchFeatureSets, fetchFeatureSetsTags: featureStoreActions.fetchFeatureSetsTags, removeFeatureSet: featureStoreActions.removeFeatureSet, diff --git a/src/constants.js b/src/constants.js index 3489bff53..688f78437 100644 --- a/src/constants.js +++ b/src/constants.js @@ -315,7 +315,9 @@ export const SET_NEW_FUNCTION_TRACK_MODELS = 'SET_NEW_FUNCTION_TRACK_MODELS' export const SET_NEW_FUNCTION_VOLUMES = 'SET_NEW_FUNCTION_VOLUMES' export const SET_NEW_FUNCTION_VOLUME_MOUNTS = 'SET_NEW_FUNCTION_VOLUME_MOUNTS' export const FUNCTION_CREATING_STATE = 'creating' +export const FUNCTION_FAILED_STATE = 'failed' export const FUNCTION_FAILED_TO_DELETE_STATE = 'failedToDelete' +export const FUNCTION_ERROR_STATE = 'error' export const FUNCTION_INITIALIZED_STATE = 'initialized' export const FUNCTION_READY_STATE = 'ready' export const FUNCTION_PENDINDG_STATE = 'pending' @@ -343,11 +345,14 @@ export const FETCH_ENTITIES_BEGIN = 'FETCH_ENTITIES_BEGIN' export const FETCH_ENTITIES_FAILURE = 'FETCH_ENTITIES_FAILURE' export const FETCH_ENTITIES_SUCCESS = 'FETCH_ENTITIES_SUCCESS' export const FETCH_ENTITY_SUCCESS = 'FETCH_ENTITY_SUCCESS' +export const FETCH_EXPANDED_FEATURE_SET_SUCCESS = 'FETCH_EXPANDED_FEATURE_SET_SUCCESS' export const FETCH_FEATURES_BEGIN = 'FETCH_FEATURES_BEGIN' export const FETCH_FEATURES_FAILURE = 'FETCH_FEATURES_FAILURE' export const FETCH_FEATURES_SUCCESS = 'FETCH_FEATURES_SUCCESS' export const FETCH_FEATURE_SETS_BEGIN = 'FETCH_FEATURE_SETS_BEGIN' export const FETCH_FEATURE_SETS_FAILURE = 'FETCH_FEATURE_SETS_FAILURE' +export const FETCH_FEATURE_SET_BEGIN = 'FETCH_FEATURE_SET_BEGIN' +export const FETCH_FEATURE_SET_FAILURE = 'FETCH_FEATURE_SET_FAILURE' export const FETCH_FEATURE_SET_SUCCESS = 'FETCH_FEATURE_SET_SUCCESS' export const FETCH_FEATURE_SETS_SUCCESS = 'FETCH_FEATURE_SETS_SUCCESS' export const FETCH_FEATURE_SUCCESS = 'FETCH_FEATURE_SUCCESS' @@ -642,6 +647,6 @@ export const CHART_TYPE_LINE = 'line' export const CHART_TYPE_BAR = 'bar' /*=========== ARTIFACTS LIMITS =============*/ -export const ARTIFACT_MAX_CHUNK_SIZE = 1048576 // 1MB -export const ARTIFACT_MAX_PREVIEW_SIZE = 10485760 // 10MB -export const ARTIFACT_MAX_DOWNLOAD_SIZE = 104857600 // 100MB +export const ARTIFACT_MAX_CHUNK_SIZE = 1048576 // 1MB +export const ARTIFACT_MAX_PREVIEW_SIZE = 10485760 // 10MB +export const ARTIFACT_MAX_DOWNLOAD_SIZE = 104857600 // 100MB diff --git a/src/reducers/featureStoreReducer.js b/src/reducers/featureStoreReducer.js index fc5e82152..803cc2201 100644 --- a/src/reducers/featureStoreReducer.js +++ b/src/reducers/featureStoreReducer.js @@ -66,7 +66,10 @@ import { START_FEATURE_SET_INGEST_SUCCESS, FETCH_FEATURE_SET_SUCCESS, SET_NEW_FEATURE_SET_CREDENTIALS_ACCESS_KEY, - PANEL_DEFAULT_ACCESS_KEY + PANEL_DEFAULT_ACCESS_KEY, + FETCH_EXPANDED_FEATURE_SET_SUCCESS, + FETCH_FEATURE_SET_BEGIN, + FETCH_FEATURE_SET_FAILURE } from '../constants' const initialState = { @@ -75,7 +78,8 @@ const initialState = { allData: [], selectedRowData: { content: {} - } + }, + featureSetLoading: false }, featureVectors: { allData: [], @@ -174,18 +178,7 @@ const featureStoreReducer = (state = initialState, { type, payload }) => { } } } - case FETCH_FEATURE_SETS_BEGIN: - return { - ...state, - loading: true - } - case FETCH_FEATURE_SETS_FAILURE: - return { - ...state, - error: payload, - loading: false - } - case FETCH_FEATURE_SET_SUCCESS: + case FETCH_EXPANDED_FEATURE_SET_SUCCESS: return { ...state, featureSets: { @@ -199,6 +192,41 @@ const featureStoreReducer = (state = initialState, { type, payload }) => { } } } + case FETCH_FEATURE_SET_BEGIN: + return { + ...state, + featureSets: { + ...state.featureSets, + featureSetLoading: true + } + } + case FETCH_FEATURE_SET_FAILURE: + return { + ...state, + featureSets: { + ...state.featureSets, + featureSetLoading: false + } + } + case FETCH_FEATURE_SET_SUCCESS: + return { + ...state, + featureSets: { + ...state.featureSets, + featureSetLoading: false + } + } + case FETCH_FEATURE_SETS_BEGIN: + return { + ...state, + loading: true + } + case FETCH_FEATURE_SETS_FAILURE: + return { + ...state, + error: payload, + loading: false + } case FETCH_FEATURE_SETS_SUCCESS: return { ...state, diff --git a/tests/mockServer/mock.js b/tests/mockServer/mock.js index 7d90161a2..f03a4a771 100644 --- a/tests/mockServer/mock.js +++ b/tests/mockServer/mock.js @@ -398,6 +398,17 @@ function getFeatureSet(req, res) { ) } + if (req.query['format'] === 'minimal') { + collectedFeatureSets = collectedFeatureSets.map(featureSet => { + const metadataFields = ['description', 'name', 'project', 'tag', 'uid', 'labels'].map( + fieldName => `metadata.${fieldName}` + ) + const specFields = ['entities', 'targets', 'engine'].map(fieldName => `spec.${fieldName}`) + + return pick(featureSet, ['kind', ...metadataFields, 'status.state', ...specFields]) + }) + } + res.send({ feature_sets: collectedFeatureSets }) } @@ -2064,7 +2075,6 @@ function postArtifact(req, res) { artifactTemplate.spec.model_file = req.body.spec.model_file } - const collectedArtifactsWithSameName = artifacts.artifacts.filter(artifact => { return ( artifact.metadata?.project === req.body.metadata.project && @@ -2079,7 +2089,7 @@ function postArtifact(req, res) { artifact.metadata.tag = null } else if (artifact.metadata?.tag === 'latest') { // when we post an artifact with custom tag we store 2 artifacts (custom and latest) - // so when we post another artifact with same name we have to delete artifact with 'latest' tag + // so when we post another artifact with same name we have to delete artifact with 'latest' tag // or we remove latest tag in case when we have only one object if ( collectedArtifactsWithSameName.find( From 90aebeffaa6773a63f248497be3f7e55df3d096b Mon Sep 17 00:00:00 2001 From: Ilank <63646693+ilan7empest@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:35:44 +0200 Subject: [PATCH 13/26] Fix [Projects] Clicking on search text box in projects refreshes the screen (#2853) --- src/components/ProjectsPage/ProjectsView.js | 2 +- src/utils/generateMonitoringData.js | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/ProjectsPage/ProjectsView.js b/src/components/ProjectsPage/ProjectsView.js index c8246eb5b..57bfad3ad 100644 --- a/src/components/ProjectsPage/ProjectsView.js +++ b/src/components/ProjectsPage/ProjectsView.js @@ -107,7 +107,7 @@ const ProjectsView = ({ /> )}
- {projectStore.projects.length > 0 && } +
diff --git a/src/utils/generateMonitoringData.js b/src/utils/generateMonitoringData.js index 29bedc23c..4b491cec5 100644 --- a/src/utils/generateMonitoringData.js +++ b/src/utils/generateMonitoringData.js @@ -47,7 +47,7 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { return tab === JOBS_MONITORING_JOBS_TAB ? { all: { - counter: data.all, + counter: data.all || 0, link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: [FILTER_ALL_ITEMS] }) }, counters: [ @@ -85,7 +85,7 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { : tab === JOBS_MONITORING_WORKFLOWS_TAB ? { all: { - counter: data.all, + counter: data.all || 0, link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: [FILTER_ALL_ITEMS] }) }, counters: [ @@ -106,7 +106,8 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { }, { counter: data.failed, - link: () => navigateToJobsMonitoringPage({ [STATUS_FILTER]: [ERROR_STATE, FAILED_STATE] }), + link: () => + navigateToJobsMonitoringPage({ [STATUS_FILTER]: [ERROR_STATE, FAILED_STATE] }), statusClass: 'failed', tooltip: 'Error, Failed' }, @@ -120,15 +121,15 @@ export const generateMonitoringStats = (data, navigate, dispatch, tab) => { } : { all: { - counter: data.all, + counter: data.all || 0, link: () => navigateToJobsMonitoringPage({ [TYPE_FILTER]: FILTER_ALL_ITEMS }, {}) }, jobs: { - counter: data.jobs, + counter: data.jobs || 0, link: () => navigateToJobsMonitoringPage({ [TYPE_FILTER]: JOB_KIND_JOB }, {}) }, workflows: { - counter: data.workflows, + counter: data.workflows || 0, link: () => navigateToJobsMonitoringPage({ [TYPE_FILTER]: JOB_KIND_WORKFLOW }, {}) } } From 57b3d5ad64bb5e874f4dadf53950055a33310f80 Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:08:18 +0200 Subject: [PATCH 14/26] Fix [Monitoring] missing tooltips on counters (#2865) --- src/elements/ProjectsMonitoringCounters/JobsCounters.js | 2 +- src/elements/ProjectsMonitoringCounters/WorkflowsCounters.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/elements/ProjectsMonitoringCounters/JobsCounters.js b/src/elements/ProjectsMonitoringCounters/JobsCounters.js index 9094c710f..09b4dbd05 100644 --- a/src/elements/ProjectsMonitoringCounters/JobsCounters.js +++ b/src/elements/ProjectsMonitoringCounters/JobsCounters.js @@ -74,7 +74,7 @@ const JobsCounters = () => { {projectStore.projectsSummary.loading ? ( ) : ( - }> + }> {counter} diff --git a/src/elements/ProjectsMonitoringCounters/WorkflowsCounters.js b/src/elements/ProjectsMonitoringCounters/WorkflowsCounters.js index 8faf553ab..a12e5d0df 100644 --- a/src/elements/ProjectsMonitoringCounters/WorkflowsCounters.js +++ b/src/elements/ProjectsMonitoringCounters/WorkflowsCounters.js @@ -83,7 +83,7 @@ const WorkflowsCounters = () => { {projectStore.projectsSummary.loading ? ( ) : ( - }> + }> {counter} From 2272835d927f36334af39ebf40011278a0a6c300 Mon Sep 17 00:00:00 2001 From: Taras-Hlukhovetskyi <155433425+Taras-Hlukhovetskyi@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:13:18 +0200 Subject: [PATCH 15/26] Fix [Models] log model with a training set that has large number of columns fail to present model feature stats (#2867) * Fix [Models] log model with a training set that has large number of columns fail to present model feature stats * fix comments --- src/common/Chart/MlChart.js | 13 +- src/components/DetailsInfo/detailsInfo.scss | 14 +- .../DetailsStatistics/DetailsStatistics.js | 144 +++++++----------- .../DetailsStatisticsTableRow.js | 109 +++++++++++++ .../DetailsStatistics/detailsStatistics.scss | 34 ++++- .../detailsStatistics.util.js | 3 + src/hooks/useVirtualization.hook.js | 42 +++-- 7 files changed, 247 insertions(+), 112 deletions(-) create mode 100644 src/components/DetailsStatistics/DetailsStatisticsTableRow.js diff --git a/src/common/Chart/MlChart.js b/src/common/Chart/MlChart.js index 4575fbbfa..265d24d1d 100644 --- a/src/common/Chart/MlChart.js +++ b/src/common/Chart/MlChart.js @@ -27,10 +27,10 @@ import './mlChart.scss' Chart.register(...registerables) -const MlChart = ({ config }) => { +const MlChart = ({ config, showLoader = true }) => { const canvasRef = useRef() const [isLoading, setIsLoading] = useState(true) - const canvasClassNames = classnames(isLoading && 'hidden') + const canvasClassNames = classnames(showLoader && isLoading && 'hidden') useLayoutEffect(() => { const ctx = canvasRef.current.getContext('2d') @@ -53,7 +53,6 @@ const MlChart = ({ config }) => { }) } } - const mlChartInstance = new Chart(ctx, { ...chartConfig, options: { @@ -61,7 +60,7 @@ const MlChart = ({ config }) => { animation: { ...chartConfig.options.animation, onComplete: () => { - setIsLoading(false) + showLoader && setIsLoading(false) if (chartConfig?.options?.animation?.onComplete) { chartConfig.options.animation.onComplete() @@ -72,13 +71,13 @@ const MlChart = ({ config }) => { }) return () => { - mlChartInstance.destroy() + mlChartInstance?.destroy() } - }, [isLoading, config]) + }, [config, showLoader]) return (
- {isLoading && } + {showLoader && isLoading && }
) diff --git a/src/components/DetailsInfo/detailsInfo.scss b/src/components/DetailsInfo/detailsInfo.scss index 54595c413..066d45f07 100644 --- a/src/components/DetailsInfo/detailsInfo.scss +++ b/src/components/DetailsInfo/detailsInfo.scss @@ -2,12 +2,18 @@ @import '~igz-controls/scss/borders'; @import '~igz-controls/scss/mixins'; +$itemInfoWithoutPadding: item-info-without-padding; + .item-info { display: flex; flex: 1; flex-wrap: wrap; - overflow: auto; padding-right: 24px; + overflow: auto; + + &:has(.#{$itemInfoWithoutPadding}) { + padding: 0; + } & > * { flex: 0 0 100%; @@ -71,8 +77,8 @@ flex: 1; align-items: center; min-width: 110px; - word-break: break-word; color: $topaz; + word-break: break-word; &_multiline { display: inline-block; @@ -155,3 +161,7 @@ width: 100%; } } + +:export { + itemInfoWithoutPadding: $itemInfoWithoutPadding; +} diff --git a/src/components/DetailsStatistics/DetailsStatistics.js b/src/components/DetailsStatistics/DetailsStatistics.js index 17775f80d..e7ae492ef 100644 --- a/src/components/DetailsStatistics/DetailsStatistics.js +++ b/src/components/DetailsStatistics/DetailsStatistics.js @@ -17,31 +17,53 @@ illegal under applicable law, and the grant of the foregoing license under the Apache 2.0 license is conditioned upon your compliance with such restriction. */ -import React, { useMemo } from 'react' +import React, { useMemo, memo } from 'react' import PropTypes from 'prop-types' import classnames from 'classnames' -import MlChart from '../../common/Chart/MlChart' import { Tooltip, TextTooltipTemplate } from 'igz-controls/components' +import DetailsStatisticsTableRow from './DetailsStatisticsTableRow' -import { generateStatistics } from './detailsStatistics.util' +import { DETAILS_STATISTICS_TABLE_BODY_ID, DETAILS_STATISTICS_TABLE_ID, generateStatistics } from './detailsStatistics.util' import { getHistogramChartConfig } from '../../utils/getHistogramChartConfig' +import { isRowRendered, useVirtualization } from '../../hooks/useVirtualization.hook' +import cssVariables from './detailsStatistics.scss' +import cssVariablesItemInfo from '../DetailsInfo/detailsInfo.scss' import './detailsStatistics.scss' -import colors from 'igz-controls/scss/colors.scss' const DetailsStatistics = ({ selectedItem }) => { - const statistics = generateStatistics(selectedItem) + const statistics = useMemo(() => generateStatistics(selectedItem), [selectedItem]) + const rowsSizes = useMemo(() => new Array(statistics.length).fill(parseInt(cssVariables.detailsStatisticsRowHeight)), [statistics]) + const heightData = useMemo( + () => ({ + headerRowHeight: cssVariables.detailsStatisticsHeaderRowHeight, + rowHeight: cssVariables.detailsStatisticsRowHeight, + rowHeightExtended: cssVariables.detailsStatisticsRowHeight + }), + [] + ) const chartConfig = useMemo(getHistogramChartConfig, []) - const headers = Object.entries(statistics[0]).map(([label, value]) => ({ - label, - type: value.type, - hidden: statistics.every(statisticsItem => statisticsItem[label].hidden) - })) + const headers = useMemo( + () => + Object.entries(statistics[0]).map(([label, value]) => ({ + label, + type: value.type, + hidden: statistics.every(statisticsItem => statisticsItem[label].hidden) + })), + [statistics] + ) + const virtualizationConfig = useVirtualization({ + heightData, + ignoreHorizontalScroll: true, + rowsSizes, + tableBodyId: DETAILS_STATISTICS_TABLE_BODY_ID, + tableId: DETAILS_STATISTICS_TABLE_ID + }) return ( -
-
+
+
{headers.map(({ label, type, hidden }) => { @@ -62,85 +84,23 @@ const DetailsStatistics = ({ selectedItem }) => { ) })}
- {statistics.map((statisticsItem, statisticsItemIndex) => { - return ( -
- {Object.values(statisticsItem).map((statisticsValue, index) => { - const statisticsItemClassNames = classnames( - 'details-statistics__table-item', - `statistics-cell__${headers[index].label}`, - `statistics-cell__type_${headers[index].type}`, - headers[index].hidden && 'statistics-cell_hidden' - ) - - let config = {} - - if (statisticsValue.type === 'chart') { - config = { - ...chartConfig, - data: { - labels: statisticsValue.value[1], - datasets: [ - { - data: statisticsValue.value[0], - showLine: false, - backgroundColor: [colors.amethyst] - } - ] - }, - options: { - ...chartConfig.options, - scales: { - ...chartConfig.options.scales, - y: { - ...chartConfig.options.scales.y, - display: true, - min: 0, - max: Math.max(...statisticsValue.value[0]), - ticks: { - ...chartConfig.options.scales.y.ticks, - stepSize: Math.max(...statisticsValue.value[0]) - } - } - } - } - } - } - - return ( -
- {statisticsValue.type.match(/icon/) && - !statisticsValue.hidden && - statisticsValue.value} - {statisticsValue.type === 'chart' && - statisticsValue.value[1]?.length > 0 && ( - - )} - {!statisticsValue.type.match(/icon|chart/) && ( - - } - > - {statisticsValue.value} - - )} -
- ) - })} -
- ) - })} +
+ {statistics.map( + (statisticsItem, statisticsItemIndex) => + isRowRendered(virtualizationConfig, statisticsItemIndex) && ( + + ) + )} +
@@ -151,4 +111,4 @@ DetailsStatistics.propTypes = { selectedItem: PropTypes.shape({}).isRequired } -export default DetailsStatistics +export default memo(DetailsStatistics) diff --git a/src/components/DetailsStatistics/DetailsStatisticsTableRow.js b/src/components/DetailsStatistics/DetailsStatisticsTableRow.js new file mode 100644 index 000000000..a44aef870 --- /dev/null +++ b/src/components/DetailsStatistics/DetailsStatisticsTableRow.js @@ -0,0 +1,109 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { memo } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +import MlChart from '../../common/Chart/MlChart' +import { Tooltip, TextTooltipTemplate } from 'igz-controls/components' + +import './detailsStatistics.scss' +import colors from 'igz-controls/scss/colors.scss' + +const DetailsStatisticsTableRow = ({ statisticsItem, headers, chartConfig }) => { + return ( +
+ {Object.values(statisticsItem).map((statisticsValue, index) => { + const statisticsItemClassNames = classnames( + 'details-statistics__table-item', + `statistics-cell__${headers[index].label}`, + `statistics-cell__type_${headers[index].type}`, + headers[index].hidden && 'statistics-cell_hidden' + ) + + let config = {} + + if (statisticsValue.type === 'chart') { + config = { + ...chartConfig, + data: { + labels: statisticsValue.value[1], + datasets: [ + { + data: statisticsValue.value[0], + showLine: false, + backgroundColor: [colors.amethyst] + } + ] + }, + options: { + ...chartConfig.options, + // Note if canvas is hidden and chart is responsive it may freeze page for a moment during chart calculations + // responsive: false, + animation: false, + scales: { + ...chartConfig.options.scales, + y: { + ...chartConfig.options.scales.y, + display: true, + min: 0, + max: Math.max(...statisticsValue.value[0]), + ticks: { + ...chartConfig.options.scales.y.ticks, + stepSize: Math.max(...statisticsValue.value[0]) + } + } + } + } + } + } + + return ( +
+ {statisticsValue.type.match(/icon/) && !statisticsValue.hidden && statisticsValue.value} + {statisticsValue.type === 'chart' && statisticsValue.value[1]?.length > 0 && ( + + )} + {!statisticsValue.type.match(/icon|chart/) && ( + + } + > + {statisticsValue.value} + + )} +
+ ) + })} +
+ ) +} + +DetailsStatisticsTableRow.propTypes = { + statisticsItem: PropTypes.shape({}).isRequired, + headers: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + chartConfig: PropTypes.shape({}).isRequired +} + +export default memo(DetailsStatisticsTableRow) diff --git a/src/components/DetailsStatistics/detailsStatistics.scss b/src/components/DetailsStatistics/detailsStatistics.scss index 2f26e55d2..f458288ef 100644 --- a/src/components/DetailsStatistics/detailsStatistics.scss +++ b/src/components/DetailsStatistics/detailsStatistics.scss @@ -2,16 +2,42 @@ @import '~igz-controls/scss/borders'; @import '~igz-controls/scss/mixins'; +$detailsStatisticsRowHeight: 45px; +$detailsStatisticsHeaderRowHeight: 49px; + .details-statistics { @include detailsMetadataStatisticsTable; + height: 100%; + &__table { + height: 100%; + overflow: auto; + &-wrapper { + width: 100%; padding: 0 0 20px 0; } &-header { - padding: 15px 0; + height: $detailsStatisticsHeaderRowHeight; + padding: 0; + border-bottom: none; + + .header-item { + padding: 15px 0; + background-color: $white; + border-bottom: $secondaryBorder; + } + } + + &-row { + height: $detailsStatisticsRowHeight; + border-bottom: none; + } + + &-item { + border-bottom: $secondaryBorder; } .statistics-cell { @@ -27,6 +53,7 @@ &_chart { flex: 1.5; + min-width: 160px; } } @@ -40,3 +67,8 @@ } } } + +:export { + detailsStatisticsRowHeight: $detailsStatisticsRowHeight; + detailsStatisticsHeaderRowHeight: $detailsStatisticsHeaderRowHeight +} diff --git a/src/components/DetailsStatistics/detailsStatistics.util.js b/src/components/DetailsStatistics/detailsStatistics.util.js index e642053e7..4f7d5d47d 100644 --- a/src/components/DetailsStatistics/detailsStatistics.util.js +++ b/src/components/DetailsStatistics/detailsStatistics.util.js @@ -26,6 +26,9 @@ import { roundFloats } from '../../utils/roundFloats' import { ReactComponent as Primary } from 'igz-controls/images/ic-key.svg' import { ReactComponent as LabelColumn } from 'igz-controls/images/ic_target-with-dart.svg' +export const DETAILS_STATISTICS_TABLE_ID = 'DETAILS_STATISTICS_TABLE_ID' +export const DETAILS_STATISTICS_TABLE_BODY_ID = 'DETAILS_STATISTICS_TABLE_BODY_ID' + export const generateStatistics = selectedItem => { return Object.entries((selectedItem?.stats || selectedItem.feature_stats) ?? {}).map( ([name, metrics]) => { diff --git a/src/hooks/useVirtualization.hook.js b/src/hooks/useVirtualization.hook.js index be8ad3ccf..9cc58150c 100644 --- a/src/hooks/useVirtualization.hook.js +++ b/src/hooks/useVirtualization.hook.js @@ -208,22 +208,32 @@ const useTableScroll = ({ * @param {string|number} options.heightData.rowHeightExtended - Height of an extended row. * @param {string|number} options.heightData.headerRowHeight - Height of the table header row. * @param {boolean} options.activateTableScroll - Boolean indicator for useTableScroll hook. + * @param {string|number} options.tableId - Custom table ID. + * @param {string|number} options.tableBodyId - Custom table body ID. + * @param {boolean} options.ignoreHorizontalScroll - Boolean indicator for ignoring horizontal scroll event * @returns {Object} - Object containing virtualization configuration. */ +const rowsDataDefault = { + content: [] +} +const rowsSizesDefault = [] + export const useVirtualization = ({ renderTriggerItem, - rowsSizes = [], - rowsData = { - content: [] - }, - heightData: { rowHeight, rowHeightExtended, headerRowHeight }, - activateTableScroll = false + rowsSizes = rowsSizesDefault, + rowsData = rowsDataDefault, + heightData: { rowHeight = 0, rowHeightExtended = 0, headerRowHeight = 0 }, + activateTableScroll = false, + tableId = null, + tableBodyId = null, + ignoreHorizontalScroll = false }) => { const [virtualizationConfig, setVirtualizationConfig] = useState(virtualizationConfigInitialState) const [rowsSizesLocal, setRowsSizesLocal] = useState(rowsSizes) const rowHeightLocal = useMemo(() => parseInt(rowHeight), [rowHeight]) const extendedRowHeightLocal = useMemo(() => parseInt(rowHeightExtended), [rowHeightExtended]) const headerRowHeightLocal = useMemo(() => parseInt(headerRowHeight), [headerRowHeight]) + const prevScrollTop = useRef(null) useLayoutEffect(() => { if (isEmpty(rowsData.content) && !isEqual(rowsSizes, rowsSizesLocal)) { @@ -255,11 +265,15 @@ export const useVirtualization = ({ ]) useLayoutEffect(() => { - const tableElement = document.getElementById(MAIN_TABLE_ID) - const tableBodyElement = document.getElementById(MAIN_TABLE_BODY_ID) + const tableElement = document.getElementById(tableId || MAIN_TABLE_ID) + const tableBodyElement = document.getElementById(tableBodyId || MAIN_TABLE_BODY_ID) const elementsHeight = sum(rowsSizesLocal) - const calculateVirtualizationConfig = throttle(() => { + const calculateVirtualizationConfig = throttle(event => { + if (ignoreHorizontalScroll && event?.type === 'scroll') { + if (tableElement.scrollTop === prevScrollTop.current) return + prevScrollTop.current = tableElement.scrollTop + } const scrollClientHeight = parseInt( tableElement.scrollTop + tableElement.clientHeight - headerRowHeightLocal ) @@ -342,7 +356,15 @@ export const useVirtualization = ({ window.removeEventListener('resize', calculateVirtualizationConfig) } } - }, [renderTriggerItem, headerRowHeightLocal, rowsSizesLocal, rowsData.content]) + }, [ + renderTriggerItem, + headerRowHeightLocal, + rowsSizesLocal, + rowsData.content, + tableId, + tableBodyId, + ignoreHorizontalScroll + ]) useTableScroll({ rowHeight: rowHeightLocal, From 00e51859996a66bb3d580d19272934f0fcb12aea Mon Sep 17 00:00:00 2001 From: illia-prokopchuk <78905712+illia-prokopchuk@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:15:30 +0200 Subject: [PATCH 16/26] Fix [Breadcrumbs] Double scroll when selecting the last project (#2863) --- .../JobWizardFunctionSelection.js | 10 ++++---- src/utils/scroll.util.js | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js index 9c2901ef7..e08340e81 100644 --- a/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js +++ b/src/components/JobWizard/JobWizardSteps/JobWizardFunctionSelection/JobWizardFunctionSelection.js @@ -92,7 +92,7 @@ const JobWizardFunctionSelection = ({ const [functionsRequestErrorMessage, setFunctionsRequestErrorMessage] = useState('') const [hubFunctionsRequestErrorMessage, setHubFunctionsRequestErrorMessage] = useState('') const selectedActiveTab = useRef(null) - const functionSelectionRef = useRef(null) + const functionsContainerRef = useRef(null) const hubFunctionLoadedRef = useRef(false) const filtersStoreHubCategories = useSelector( @@ -362,14 +362,14 @@ const JobWizardFunctionSelection = ({ const isTabActive = selectedActiveTab.current && selectedActiveTab.current === activeTab if (stepIsActive && isTabActive) { - scrollToElement(functionSelectionRef, '.selected') + scrollToElement(functionsContainerRef, '.selected') } else if (!stepIsActive && !isTabActive) { setActiveTab(selectedActiveTab.current) } }, [stepIsActive, activeTab, setActiveTab, selectedActiveTab]) return ( -
+
Function selection
@@ -404,7 +404,7 @@ const JobWizardFunctionSelection = ({ isEmpty(functions)) ? ( ) : ( -
+
{(filteredFunctions.length > 0 ? filteredFunctions : functions) .sort((prevFunc, nextFunc) => prevFunc.name.localeCompare(nextFunc.name)) .map(functionData => { @@ -455,7 +455,7 @@ const JobWizardFunctionSelection = ({ isEmpty(templates)) ? ( ) : ( -
+
{filteredTemplates .sort((prevTemplate, nextTemplate) => prevTemplate.metadata.name.localeCompare(nextTemplate.metadata.name) diff --git a/src/utils/scroll.util.js b/src/utils/scroll.util.js index fed18fcfd..9316e74d3 100644 --- a/src/utils/scroll.util.js +++ b/src/utils/scroll.util.js @@ -31,10 +31,23 @@ export const scrollToElement = ( shouldScrollToTop ? parentRef.current.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) : setTimeout(() => { - selectedElement.scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'start' - }) + scrollToCenter(parentRef?.current, selectedElement) }, timeoutDuration) } + +const scrollToCenter = (container, element) => { + const containerRect = container.getBoundingClientRect() + const elementRect = element.getBoundingClientRect() + + const scrollTop = + container.scrollTop + + elementRect.top - + containerRect.top - + container.clientHeight / 2 + + elementRect.height / 2 + + container.scrollTo({ + top: scrollTop, + behavior: 'smooth' + }) +} From c0d2870a708fa90d9ff9d127917d1b8fa1380261 Mon Sep 17 00:00:00 2001 From: Taras-Hlukhovetskyi <155433425+Taras-Hlukhovetskyi@users.noreply.github.com> Date: Wed, 6 Nov 2024 23:18:07 +0200 Subject: [PATCH 17/26] Impl [Model Endpoints, Real-time Pipelines] Change filters to new components (#2859) * Impl [Model Endpoints, Real-time Pipelines] Change filters to new components * fixed issue with refresh on pipelines * Updated tests due to ML-8117 * Updated test for Real-Time Pipelines --------- Co-authored-by: Olena Zhelnytska --- src/components/ActionBar/ActionBar.js | 6 +- .../ModelEndpoints/ModelEndpoints.js | 78 ++++++++++++------- .../ModelEndpoints/ModelEndpointsFilters.js | 46 +++++++++++ .../ModelEndpoints/modelEndpoints.util.js | 14 +--- .../RealTimePipelines/RealTimePipelines.js | 15 ++-- .../realTimePipelines.util.js | 4 +- src/reducers/filtersReducer.js | 22 +++++- tests/features/common-tools/common-consts.js | 4 + .../features/common/actions/common.action.js | 13 ++++ .../features/common/page-objects/models.po.js | 49 +++++------- .../page-objects/project-settings.po.js | 4 +- tests/features/models.feature | 44 ++++++++--- tests/features/projectsPage.feature | 1 - tests/features/step-definitions/steps.js | 15 +++- 14 files changed, 221 insertions(+), 94 deletions(-) create mode 100644 src/components/ModelsPage/ModelEndpoints/ModelEndpointsFilters.js diff --git a/src/components/ActionBar/ActionBar.js b/src/components/ActionBar/ActionBar.js index 6408559c9..d7bd0871c 100644 --- a/src/components/ActionBar/ActionBar.js +++ b/src/components/ActionBar/ActionBar.js @@ -76,7 +76,7 @@ const ActionBar = ({ }) => { const [autoRefresh, setAutoRefresh] = useState(autoRefreshIsEnabled) const filtersStore = useSelector(store => store.filtersStore) - const filterMenu = useSelector(store => store.filtersStore[FILTER_MENU][filterMenuName].values) + const filterMenu = useSelector(store => store.filtersStore[FILTER_MENU][filterMenuName]?.values ?? {}) const filterMenuModal = useSelector( store => store.filtersStore[FILTER_MENU_MODAL][filterMenuName] ) @@ -204,7 +204,7 @@ const ActionBar = ({ ) handleRefresh({ ...formState.values, - ...filtersStore.filterMenuModal[filterMenuName].values + ...filtersStore.filterMenuModal[filterMenuName]?.values ?? {} }) } }, @@ -283,7 +283,7 @@ const ActionBar = ({ - applyChanges({ ...formState.values, name: value }, filterMenuModal.values) + applyChanges({ ...formState.values, name: value }, filterMenuModal?.values ?? {}) } />
diff --git a/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js b/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js index 287cdcf89..65a3b6d67 100644 --- a/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js +++ b/src/components/ModelsPage/ModelEndpoints/ModelEndpoints.js @@ -20,14 +20,15 @@ such restriction. import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useLocation, useNavigate, useParams } from 'react-router-dom' -import { isEmpty, orderBy } from 'lodash' +import { isEmpty } from 'lodash' import ArtifactsTableRow from '../../../elements/ArtifactsTableRow/ArtifactsTableRow' -import FilterMenu from '../../FilterMenu/FilterMenu' import Loader from '../../../common/Loader/Loader' import ModelsPageTabs from '../ModelsPageTabs/ModelsPageTabs' import NoData from '../../../common/NoData/NoData' import Table from '../../Table/Table' +import ActionBar from '../../ActionBar/ActionBar' +import ModelEndpointsFilters from './ModelEndpointsFilters' import { GROUP_BY_NONE, @@ -37,12 +38,17 @@ import { } from '../../../constants' import { createModelEndpointsRowData } from '../../../utils/createArtifactsContent' import { fetchModelEndpoints, removeModelEndpoints } from '../../../reducers/artifactsReducer' -import { chooseOrFetchModelEndpoint, filters, generatePageData } from './modelEndpoints.util' +import { + chooseOrFetchModelEndpoint, + filtersConfig, + generatePageData +} from './modelEndpoints.util' import { getNoDataMessage } from '../../../utils/getNoDataMessage' import { isDetailsTabExists } from '../../../utils/link-helper.util' import { setFilters } from '../../../reducers/filtersReducer' import { useModelsPage } from '../ModelsPage.context' import { isRowRendered, useVirtualization } from '../../../hooks/useVirtualization.hook' +import { useSortTable } from '../../../hooks/useSortTable.hook' import { ReactComponent as MonitorIcon } from 'igz-controls/images/monitor-icon.svg' import { ReactComponent as Yaml } from 'igz-controls/images/yaml.svg' @@ -91,15 +97,20 @@ const ModelEndpoints = () => { { label: 'View YAML', icon: , - onClick: modelEndpointMin => chooseOrFetchModelEndpoint( - dispatch, - selectedModelEndpoint, - modelEndpointMin - ).then(toggleConvertedYaml) + onClick: modelEndpointMin => + chooseOrFetchModelEndpoint(dispatch, selectedModelEndpoint, modelEndpointMin).then( + toggleConvertedYaml + ) } ] ], - [dispatch, frontendSpec.model_monitoring_dashboard_url, handleMonitoring, selectedModelEndpoint, toggleConvertedYaml] + [ + dispatch, + frontendSpec.model_monitoring_dashboard_url, + handleMonitoring, + selectedModelEndpoint, + toggleConvertedYaml + ] ) const fetchData = useCallback( @@ -150,7 +161,7 @@ const ModelEndpoints = () => { useEffect(() => { fetchData({}) - dispatch(setFilters({ groupBy: GROUP_BY_NONE, sortBy: 'function' })) + dispatch(setFilters({ groupBy: GROUP_BY_NONE })) }, [dispatch, fetchData]) useEffect(() => { @@ -191,21 +202,28 @@ const ModelEndpoints = () => { } }, [navigate, location, pageData.details.menu, params.name, params.tag, params.tab]) - const sortedContent = useMemo(() => { - const path = filtersStore.sortBy === 'function' ? 'spec.function_uri' : 'spec.model' - - return orderBy(modelEndpoints, [path], ['asc']) - }, [modelEndpoints, filtersStore.sortBy]) - const tableContent = useMemo(() => { - return sortedContent.map(contentItem => + return modelEndpoints.map(contentItem => createModelEndpointsRowData(contentItem, params.projectName) ) - }, [params.projectName, sortedContent]) + }, [modelEndpoints, params.projectName]) + + const tableHeaders = useMemo(() => tableContent[0]?.content ?? [], [tableContent]) + + const { sortTable, selectedColumnName, getSortingIcon, sortedTableContent, sortedTableHeaders } = + useSortTable({ + headers: tableHeaders, + content: tableContent, + sortConfig: { + allowSortBy: ['name', 'function'], + defaultSortBy: 'function', + defaultDirection: 'asc' + } + }) const virtualizationConfig = useVirtualization({ rowsData: { - content: tableContent, + content: sortedTableContent, selectedItem: selectedModelEndpoint }, heightData: { @@ -217,26 +235,31 @@ const ModelEndpoints = () => { return ( <> - {(artifactsStore.modelEndpoints.modelEndpointLoading || artifactsStore.modelEndpoints.loading) && } + {(artifactsStore.modelEndpoints.modelEndpointLoading || + artifactsStore.modelEndpoints.loading) && }
- + > + +
{artifactsStore.modelEndpoints.loading ? null : modelEndpoints.length === 0 ? ( { selectedItem={selectedModelEndpoint} tab={MODEL_ENDPOINTS_TAB} tableClassName="model-endpoints-table" - tableHeaders={tableContent[0]?.content ?? []} + tableHeaders={sortedTableHeaders} virtualizationConfig={virtualizationConfig} + sortProps={{ sortTable, selectedColumnName, getSortingIcon }} > - {tableContent.map( + {sortedTableContent.map( (tableItem, index) => isRowRendered(virtualizationConfig, index) && ( { + const form = useForm() + + const handleLabelsChange = value => { + form.change(LABELS_FILTER, value || '') + } + + return ( +
+
+ + +
+
+ ) +} + +export default ModelEndpointsFilters diff --git a/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js b/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js index 98360ea62..8ac7985c8 100644 --- a/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js +++ b/src/components/ModelsPage/ModelEndpoints/modelEndpoints.util.js @@ -19,20 +19,14 @@ such restriction. */ import { isEmpty } from 'lodash' -import { LABELS_FILTER, MODEL_ENDPOINTS_TAB, MODELS_PAGE, SORT_BY } from '../../../constants' +import { LABELS_FILTER, MODEL_ENDPOINTS_TAB, MODELS_PAGE } from '../../../constants' import { TERTIARY_BUTTON } from 'igz-controls/constants' -import { filterSelectOptions } from '../../FilterMenu/filterMenu.settings' import { showErrorNotification } from '../../../utils/notifications.util' import { fetchModelEndpoint } from '../../../reducers/artifactsReducer' -export const filters = [ - { type: LABELS_FILTER, label: 'Labels:' }, - { - type: SORT_BY, - label: 'Sort By:', - options: [{ label: 'Function', id: 'function' }, ...filterSelectOptions.sortBy] - } -] +export const filtersConfig = { + [LABELS_FILTER]: { label: 'Labels:' } +} const infoHeaders = [ { label: 'UID', id: 'uid' }, diff --git a/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.js b/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.js index 8a46ffc8a..63fd79812 100644 --- a/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.js +++ b/src/components/ModelsPage/RealTimePipelines/RealTimePipelines.js @@ -23,13 +23,13 @@ import { useNavigate, useParams } from 'react-router-dom' import classnames from 'classnames' import { isNil } from 'lodash' -import FilterMenu from '../../FilterMenu/FilterMenu' import Loader from '../../../common/Loader/Loader' import ModelsPageTabs from '../ModelsPageTabs/ModelsPageTabs' import NoData from '../../../common/NoData/NoData' import Pipeline from '../../Pipeline/Pipeline' import RealTimePipelinesTableRow from '../../../elements/RealTimePipelinesTableRow/RealTimePipelinesTableRow' import Table from '../../Table/Table' +import ActionBar from '../../ActionBar/ActionBar' import { GROUP_BY_NAME, @@ -39,7 +39,7 @@ import { } from '../../../constants' import createRealTimePipelinesContent from '../../../utils/createRealTimePipelinesContent' import { fetchArtifactsFunctions, removePipelines } from '../../../reducers/artifactsReducer' -import { filters, generatePageData } from './realTimePipelines.util' +import { filtersConfig, generatePageData } from './realTimePipelines.util' import { getNoDataMessage } from '../../../utils/getNoDataMessage' import { isRowRendered, useVirtualization } from '../../../hooks/useVirtualization.hook' import { setFilters } from '../../../reducers/filtersReducer' @@ -167,10 +167,11 @@ const RealTimePipelines = () => {
-