.
**/
require_once dirname(__FILE__).'/../../include/CWebTest.php';
require_once dirname(__FILE__).'/../behaviors/CMessageBehavior.php';
define('LOGIN', true);
define('WITHOUT_LOGIN', false);
define('DEFAULT_PAGE', null);
/**
* @backup widget
*
* @dataSource AllItemValueTypes
*
* @onBefore prepareData
*/
class testDashboardPieChartWidget extends testWidgets {
protected static $dashboard_id;
protected static $disposable_dashboard_id;
protected static $item_ids;
const TYPE_ITEM_PATTERN = 'Item pattern';
const TYPE_ITEM_LIST = 'Item list';
const TYPE_DATA_SET_CLONE = 'Clone';
const HOST_NAME_ITEM_LIST = 'pie-chart-item-list';
const HOST_NAME_SCREENSHOTS = 'pie-chart-display';
const PAGE_1 = 'Page 1';
const PAGE_2 = 'Page 2';
/**
* Attach MessageBehavior to the test.
*
* @return array
*/
public function getBehaviors() {
return [
CMessageBehavior::class,
CTableBehavior::class
];
}
/**
* Create the initial data and set static variables.
*/
public function prepareData() {
// For faster tests set Pie chart as the default widget type.
DB::delete('profiles', ['idx' => 'web.dashboard.last_widget_type', 'userid' => 1]);
DB::insert('profiles', [
[
'profileid' => 99999,
'userid' => 1,
'idx' => 'web.dashboard.last_widget_type',
'value_str' => 'piechart',
'type' => 3
]
]);
// Create a Dashboard for widgets.
$fields = [
['name' => 'ds.0.hosts.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'Test Host'],
['name' => 'ds.0.items.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'Test Items'],
['name' => 'ds.0.color', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'FF465C']
];
$dashboards = CDataHelper::call('dashboard.create', [
'name' => 'Pie chart dashboard',
'auto_start' => 0,
'pages' => [
[
'name' => self::PAGE_1
],
[
'name' => self::PAGE_2,
'widgets' => [
[
'name' => 'Pie chart for simple update',
'type' => 'piechart',
'x' => 0,
'y' => 0,
'width' => 18,
'height' => 4,
'fields' => $fields
],
[
'name' => 'Pie chart for delete',
'type' => 'piechart',
'x' => 18,
'y' => 0,
'width' => 18,
'height' => 4,
'fields' => $fields
],
[
'name' => 'Pie chart for cancel',
'type' => 'piechart',
'x' => 36,
'y' => 0,
'width' => 18,
'height' => 4,
'fields' => $fields
]
]
]
]
]);
self::$dashboard_id = $dashboards['dashboardids'][0];
// Create a host for Pie chart testing.
$response = CDataHelper::createHosts([
[
'host' => self::HOST_NAME_ITEM_LIST,
'groups' => [['groupid' => 6]], // Virtual machines
'items' => [
[
'name' => 'item-1',
'key_' => 'key-1',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_UINT64
],
[
'name' => 'item-2',
'key_' => 'key-2',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_UINT64
]
]
],
[
'host' => self::HOST_NAME_SCREENSHOTS,
'groups' => [['groupid' => 6]], // Virtual machines
'items' => [
[
'name' => 'item-1',
'key_' => 'item-1',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_UINT64
],
[
'name' => 'item-2',
'key_' => 'item-2',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_UINT64
],
[
'name' => 'item-3',
'key_' => 'item-3',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_FLOAT
],
[
'name' => 'item-4',
'key_' => 'item-4',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_FLOAT
],
[
'name' => 'item-5',
'key_' => 'item-5',
'type' => ITEM_TYPE_TRAPPER,
'value_type' => ITEM_VALUE_TYPE_FLOAT
]
]
]
]);
// Get itemids from host self::HOST_NAME_SCREENSHOTS as array item_key => item_value
self::$item_ids = array_slice(CDataHelper::getIds('key_'), 2);
}
/**
* Test the elements and layout of the Pie chart create form.
*/
public function testDashboardPieChartWidget_Layout() {
// Open the create form.
$dashboard = $this->openDashboard();
$form = $dashboard->edit()->addWidget()->asForm();
$dialog = COverlayDialogElement::find()->one();
$this->assertEquals('Add widget', $dialog->getTitle());
// Check modal Help and Close buttons.
foreach (['xpath:.//*[@title="Help"]', 'xpath:.//button[@title="Close"]'] as $selector) {
$this->assertTrue($dialog->query($selector)->one()->isClickable());
}
// Assert that the generic widget Type field works as expected.
$form->fill(['Type' => CFormElement::RELOADABLE_FILL('Clock')]);
$this->assertFalse($form->query('id:data_set')->exists());
$form->fill(['Type' => CFormElement::RELOADABLE_FILL('Pie chart')]);
$this->assertTrue($form->query('id:data_set')->exists());
// Check other generic widget fields.
$expected_values = [
'Type' => 'Pie chart',
'Show header' => true,
'Name' => '',
'Refresh interval' => 'Default (1 minute)'
];
$form->checkValue($expected_values);
$this->assertFieldAttributes($form, 'Name', ['placeholder' => 'default', 'maxlength' => 255]);
$this->assertEquals(array_keys($expected_values), $form->getLabels(CElementFilter::CLICKABLE)->asText());
foreach (array_keys($expected_values) as $field) {
$this->assertTrue($form->getField($field)->isEnabled());
}
// Check tabs.
$this->assertEquals(['Data set', 'Displaying options', 'Time period', 'Legend'], $form->getTabs());
// Check Data set - Item pattern.
$expected_values = [
'xpath:.//input[@id="ds_0_color"]/..' => 'FF465C', // data set color
'xpath:.//div[@id="ds_0_hosts_"]/..' => '', // host pattern
'xpath:.//div[@id="ds_0_items_"]/..' => '', // item pattern
'Aggregation function' => 'last',
'Data set aggregation' => 'not used',
'Data set label' => ''
];
$form->checkValue($expected_values);
$expected_labels = ['Data set #1', 'Aggregation function', 'Data set aggregation', 'Data set label'];
$data_set_tab = $form->query('id:data_set')->one();
$this->assertAllVisibleLabels($data_set_tab, $expected_labels);
$this->validateDataSetHintboxes($form);
$buttons = [
'id:ds_0_hosts_', // host multiselect
'id:ds_0_items_', // item multiselect
'xpath:.//li[@data-set="0"]//button[@title="Delete"]', // first data set delete icon
'id:dataset-add', // button 'Add new data set'
'id:dataset-menu' // context menu of button 'Add new data set'
];
foreach ($buttons as $selector) {
$this->assertTrue($form->query($selector)->one()->isClickable());
}
$options = [
'Aggregation function' => ['last', 'min', 'max', 'avg', 'count', 'sum', 'first'],
'Data set aggregation' => ['not used', 'min', 'max', 'avg', 'count', 'sum']
];
foreach ($options as $dropdown => $expected_options) {
$this->assertEquals($expected_options, $form->getField($dropdown)->getOptions()->asText());
}
foreach (['id:ds_0_hosts_' => 'host patterns','id:ds_0_items_' => 'item patterns'] as $selector => $placeholder) {
$this->assertFieldAttributes($form, $selector, ['placeholder' => $placeholder], true);
}
$this->assertFieldAttributes($form, 'Data set label', ['placeholder' => 'Data set #1', 'maxlength' => 255]);
// Check Data set - Item list.
$this->addNewDataSet($form, self::TYPE_ITEM_LIST);
$form->invalidate();
$expected_values = [
'Aggregation function' => 'last',
'Data set aggregation' => 'not used',
'Data set label' => ''
];
$form->checkValue($expected_values);
$expected_labels = ['Data set #1', 'Data set #2', 'Aggregation function', 'Data set aggregation', 'Data set label'];
$this->assertAllVisibleLabels($data_set_tab, $expected_labels);
$this->validateDataSetHintboxes($form);
$buttons = [
'xpath:.//li[@data-set="1"]//button[@title="Delete"]', // second data set delete icon
'id:dataset-add', // button 'Add new data set'
'id:dataset-menu' // context menu of button 'Add new data set'
];
foreach ($buttons as $selector) {
$this->assertTrue($form->query($selector)->one()->isClickable());
}
foreach ($options as $dropdown => $expected_options) {
$this->assertEquals($expected_options, $form->getField($dropdown)->getOptions()->asText());
}
$this->assertFieldAttributes($form, 'Data set label', ['placeholder' => 'Data set #2', 'maxlength' => 255]);
// Displaying options tab.
$form->selectTab('Displaying options');
$displaying_options_tab = $this->query('id:displaying_options')->one()->waitUntilVisible();
$form->invalidate();
$expected_values = [
'History data selection' => 'Auto',
'Draw' => 'Pie',
'Space between sectors' => '1',
'id:merge' => false, // 'Merge sectors smaller than' checkbox
'id:merge_percent' => '1', // 'Merge sectors smaller than' input
'id:merge_color' => '768D99' // 'Merge sectors smaller than' color picker
];
$form->checkValue($expected_values);
$expected_labels = ['History data selection', 'Draw', 'Space between sectors', 'Merge sectors smaller than'];
$this->assertAllVisibleLabels($displaying_options_tab, $expected_labels);
$radios = ['History data selection' => ['Auto', 'History', 'Trends'], 'Draw' => ['Pie', 'Doughnut']];
foreach ($radios as $radio => $labels) {
$radio_element = $form->getField($radio);
$radio_element->isEnabled();
$this->assertEquals($labels, $radio_element->getLabels()->asText());
}
$this->assertRangeSliderParameters($form, 'Space between sectors', ['min' => '0', 'max' => '10', 'step' => '1']);
// Check states of the checkbox and input elements for 'Merge sectors smaller than' field.
foreach ([false, true] as $state) {
$form->fill(['id:merge' => $state]);
$form->invalidate();
$this->assertTrue($form->query('id:merge_percent')->one()->isEnabled($state));
$this->assertTrue($form->query('id:merge_color')->one()->isEnabled($state));
}
$form->fill(['Draw' => 'Doughnut']);
$this->query('id:show_total_fields')->one()->waitUntilVisible();
$form->invalidate();
$inputs_enabled = [
'Width' => true,
'Stroke width' => true,
'Show total value' => true,
'Size' => false,
'Decimal places' => false,
'Units' => false,
'Bold' => false,
'Colour' => false
];
$expected_labels = array_merge($expected_labels, array_keys($inputs_enabled));
$this->assertAllVisibleLabels($displaying_options_tab, $expected_labels);
$this->assertRangeSliderParameters($form, 'Width', ['min' => '20', 'max' => '50', 'step' => '10']);
$this->assertRangeSliderParameters($form, 'Stroke width', ['min' => '0', 'max' => '10', 'step' => '1']);
$form->checkValue(['Space between sectors' => 1, 'Width' => 50, 'Stroke width' => 0]);
$expected_values = [
'Show total value' => false,
'Size' => 'Auto',
'Decimal places' => '2',
'Units' => false, // 'Units' enable checkbox
'id:units' => '', // 'Units' input
'Bold' => false,
'Colour' => null
];
$form->checkValue($expected_values);
foreach ($inputs_enabled as $label => $enabled) {
$this->assertEquals($enabled, $form->getField($label)->isEnabled());
}
$field_maxlengths = [
'id:space' => 2,
'id:merge_percent' => 2,
'id:width' => 2,
'Decimal places' => 1,
'id:units' => 255
];
foreach ($field_maxlengths as $field_selector => $maxlength) {
$this->assertFieldAttributes($form, $field_selector, ['maxlength' => $maxlength]);
}
$form->fill(['Show total value' => true]);
$inputs_enabled = [
'Size' => true,
'Decimal places' => true,
'Units' => false,
'Bold' => true,
'Colour' => true
];
foreach($inputs_enabled as $label => $enabled) {
$this->assertEquals($enabled, $form->getField($label)->isEnabled());
}
$form->fill(['Size' => 'Custom']);
$value_size = $form->getField('id:value_size_custom_input');
$this->assertTrue($value_size->isVisible() && $value_size->isEnabled(),
'The "Units" field input is not visible or not enabled'
);
$this->assertEquals('20', $value_size->getValue());
$form->fill(['id:units_show' => true]);
$this->assertTrue($form->getField('Units')->isEnabled());
// Time period tab.
$form->selectTab('Time period');
$time_period_tab = $this->query('id:time_period')->one()->waitUntilVisible();
$form->invalidate();
$this->assertAllVisibleLabels($time_period_tab, ['Time period']);
$form->checkValue(['Time period' => 'Dashboard']);
$time_period = $form->getField('Time period');
$this->assertTrue($time_period->isEnabled());
$this->assertEquals(['Dashboard', 'Widget', 'Custom'], $time_period->getLabels()->asText());
$form->fill(['Time period' => 'Widget']);
$form->checkValue(['Widget' => '']);
$widget_field = $form->getField('Widget');
$this->assertTrue($widget_field->isVisible(), $widget_field->isEnabled(), $form->isRequired('Widget'),
'Widget field is not interactable or is not required'
);
$this->assertAllVisibleLabels($time_period_tab, ['Time period', 'Widget']);
$this->assertFieldAttributes($form, 'Widget', ['placeholder' => 'type here to search'], true);
$widget_field->query('button:Select')->waitUntilClickable()->one()->click();
$widget_dialog = COverlayDialogElement::find()->waitUntilReady()->all()->last();
$this->assertEquals('Widget', $widget_dialog->getTitle());
$widget_dialog->close();
$form->fill(['Time period' => 'Custom']);
$this->assertAllVisibleLabels($time_period_tab, ['Time period', 'From', 'To']);
$form->checkValue(['From' => 'now-1h', 'To' => 'now']);
foreach (['From', 'To'] as $label) {
$field = $form->getField($label);
$this->assertTrue($field->isVisible() && $field->isEnabled() && $form->isRequired($label),
'Field "'.$label.'" not all of these: visible, enabled, required.'
);
$this->assertTrue($field->query('id', 'time_period_'.strtolower($label).'_calendar')->one()->isClickable());
$this->assertFieldAttributes($form, $label, ['placeholder' => 'YYYY-MM-DD hh:mm:ss', 'maxlength' => 255], true);
}
// Legend tab.
$form->selectTab('Legend');
$legend_tab = $this->query('id:legend_tab')->one()->waitUntilVisible();
$form->invalidate();
$expected_values = [
'Show legend' => true,
'Show value' => false,
'Show aggregation function' => false,
'Rows' => 'Fixed',
'Number of rows' => 1,
'Number of columns' => 4
];
$form->checkValue($expected_values);
$this->assertAllVisibleLabels($legend_tab, array_keys($expected_values));
$this->assertEquals(['Fixed', 'Variable'], $form->getField('Rows')->getLabels()->asText());
$this->assertRangeSliderParameters($form, 'Number of rows', ['min' => '1', 'max' => '10', 'step' => '1']);
$this->assertRangeSliderParameters($form, 'Number of columns', ['min' => '1', 'max' => '4', 'step' => '1']);
$form->fill(['Show legend' => false]);
foreach (['Show value', 'Show aggregation function', 'Rows', 'Number of rows', 'Number of columns'] as $label) {
$field = $form->getField($label);
$this->assertFalse($field->isEnabled());
$this->assertTrue($field->isVisible());
}
// Footer buttons.
$this->assertEquals(['Add', 'Cancel'],
$dialog->getFooter()->query('button')->all()->filter(CElementFilter::CLICKABLE)->asText()
);
$dialog->close();
}
public function getPieChartData() {
return [
// Mandatory fields only.
[
[
'fields' => []
]
],
// Mandatory fields only - Data set type Item list.
[
[
'fields' => [
'Data set' => [
'type' => self::TYPE_ITEM_LIST,
'host' => self::HOST_NAME_ITEM_LIST,
'items' => [
['name' =>'item-1']
]
]
]
]
],
// Largest number of fields possible. Data set aggregation has to be 'none' because of Total type item.
[
[
'fields' => [
'main_fields' => [
'Name' => 'Test most fields possible',
'Show header' => false,
'Refresh interval' => '30 seconds'
],
'Data set' => [
[
'host' => ['Host*', 'one', 'two'],
'item' => ['Item*', 'one', 'two'],
'color' => '00BCD4',
'Aggregation function' => 'min',
'Data set aggregation' => 'not used',
'Data set label' => 'Label 1'
],
[
'type' => self::TYPE_ITEM_LIST,
'host' => self::HOST_NAME_ITEM_LIST,
'Aggregation function' => 'max',
'Data set aggregation' => 'not used',
'Data set label' => 'Label 2',
'items' => [
[
'name' => 'item-1',
'il_color' => '000000',
'il_type' => 'Total'
],
[
'name' => 'item-2'
]
]
]
],
'Displaying options' => [
'History data selection' => 'History',
'Draw' => 'Doughnut',
'Width' => '40',
'Stroke width' => '5',
'Space between sectors' => '2',
'id:merge' => true,
'id:merge_percent' => '10',
'xpath:.//input[@id="merge_color"]/..' => 'EEFF22',
'Show total value' => true,
'id:value_size_type' => 'Custom',
'id:value_size_custom_input' => '25',
'Decimal places' => '1',
'id:units_show' => true,
'id:units' => 'GG',
'Bold' => true,
'Colour' => '4FC3F7'
],
'Time period' => [
'Time period' => 'Custom',
'From' => 'now-3h',
'To' => 'now-2h'
],
'Legend' => [
'Show legend' => true,
'Show value' => true,
'Show aggregation function' => true,
'Rows' => 'Variable',
'Maximum number of rows' => 2
]
]
]
],
// Several data sets.
[
[
'fields' => [
'Data set' => [[], [], [], [], []]
]
]
],
// Unicode values.
[
[
'fields' => [
'main_fields' => [
'Name' => '🙂🙃 '
],
'Data set' => [
'host' => ' ',
'item' => ' ',
'Data set label' => '🙂🙃 '
],
'Displaying options' => [
'Draw' => 'Doughnut',
'Show total value' => true,
'id:units_show' => true,
'id:units' => '🙂🙃 '
]
]
]
],
// Different Time period formats.
[
[
'fields' => [
'Time period' => [
'Time period' => 'Custom',
'From' => '2020',
'To' => '2020-10-10 00:00:00'
]
]
]
],
// Missing Data set.
[
[
'expected' => TEST_BAD,
'fields' => [
'delete_data_set' => true
],
'error' => 'Invalid parameter "Data set": cannot be empty.'
]
],
// Missing Host pattern.
[
[
'expected' => TEST_BAD,
'fields' => [
'remake_data_set' => true,
'Data set' => ['item' => '*']
],
'error' => 'Invalid parameter "Data set/1/hosts": cannot be empty.'
]
],
// Missing Item pattern.
[
[
'expected' => TEST_BAD,
'fields' => [
'remake_data_set' => true,
'Data set' => ['host' => '*']
],
'error' => 'Invalid parameter "Data set/1/items": cannot be empty.'
]
],
// Missing Host AND Item pattern.
[
[
'expected' => TEST_BAD,
'fields' => [
'remake_data_set' => true,
'Data set' => ['item' => '', 'host' => '']
],
'error' => 'Invalid parameter "Data set/1/hosts": cannot be empty.'
]
],
// Missing Item list.
[
[
'expected' => TEST_BAD,
'fields' => [
'Data set' => [
'type' => self::TYPE_ITEM_LIST
]
],
'error' => 'Invalid parameter "Data set/1/itemids": cannot be empty.'
]
],
// Merge sector % value too small.
[
[
'expected' => TEST_BAD,
'fields' => [
'Displaying options' => [
'id:merge' => true,
'id:merge_percent' => '0'
]
],
'error' => 'Invalid parameter "merge_percent": value must be one of 1-10.'
]
],
// Merge sector % value too big.
[
[
'expected' => TEST_BAD,
'fields' => [
'Displaying options' => [
'id:merge' => true,
'id:merge_percent' => '11'
]
],
'error' => 'Invalid parameter "merge_percent": value must be one of 1-10.'
]
],
// Total value custom size missing.
[
[
'expected' => TEST_BAD,
'fields' => [
'Displaying options' => [
'Draw' => 'Doughnut',
'Show total value' => true,
'id:value_size_type' => 'Custom',
'id:value_size_custom_input' => ''
]
],
'error' => 'Invalid parameter "value_size": value must be one of 1-100.'
]
],
// Total value custom size out of range.
[
[
'expected' => TEST_BAD,
'fields' => [
'Displaying options' => [
'Draw' => 'Doughnut',
'Show total value' => true,
'id:value_size_type' => 'Custom',
'id:value_size_custom_input' => '101'
]
],
'error' => 'Invalid parameter "value_size": value must be one of 1-100.'
]
],
// Decimal places value too big.
[
[
'expected' => TEST_BAD,
'fields' => [
'Displaying options' => [
'Draw' => 'Doughnut',
'Show total value' => true,
'Decimal places' => '7'
]
],
'error' => 'Invalid parameter "Decimal places": value must be one of 0-6.'
]
],
// Empty Time period (Widget).
[
[
'expected' => TEST_BAD,
'fields' => [
'Time period' => [
'Time period' => 'Widget'
]
],
'error' => 'Invalid parameter "Time period/Widget": cannot be empty.'
]
],
// Empty Time period (Custom).
[
[
'expected' => TEST_BAD,
'fields' => [
'Time period' => [
'Time period' => 'Custom',
'From' => '',
'To' => ''
]
],
'error' => [
'Invalid parameter "Time period/From": cannot be empty.',
'Invalid parameter "Time period/To": cannot be empty.'
]
]
],
// Invalid Time period (Custom).
[
[
'expected' => TEST_BAD,
'fields' => [
'Time period' => [
'Time period' => 'Custom',
'From' => '0',
'To' => '2020-13-32'
]
],
'error' => [
'Invalid parameter "Time period/From": a time is expected.',
'Invalid parameter "Time period/To": a time is expected.'
]
]
],
// Bad color values.
[
[
'expected' => TEST_BAD,
'fields' => [
'Data set' => [
'host' => 'Host*',
'item' => 'Item*',
'color' => 'FFFFFG'
],
'Displaying options' => [
'id:merge' => true,
'xpath:.//input[@id="merge_color"]/..' => 'FFFFFG',
'Draw' => 'Doughnut',
'Show total value' => true,
'Colour' => 'FFFFFG'
]
],
'error' => [
'Invalid parameter "Data set/1/color": a hexadecimal colour code (6 symbols) is expected.',
'Invalid parameter "merge_color": a hexadecimal colour code (6 symbols) is expected.',
'Invalid parameter "Colour": a hexadecimal colour code (6 symbols) is expected.'
]
]
]
];
}
/**
* Test creation of Pie chart.
*
* @dataProvider getPieChartData
*/
public function testDashboardPieChartWidget_Create($data){
$this->createUpdatePieChart($data);
}
/**
* Creates the base widget used for the update scenario.
*/
public function prepareUpdatePieChart(){
$providedData = $this->getProvidedData();
$data = reset($providedData);
// Create a dashboard with the widget for updating.
$response = CDataHelper::call('dashboard.create', [
'name' => 'Pie chart dashboard '.md5(serialize($data)),
'pages' => [
[
'widgets' => [
[
'name' => 'Edit widget',
'type' => 'piechart',
'width' => 36,
'height' => 8,
'fields' => [
['name' => 'ds.0.hosts.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'Test Host'],
['name' => 'ds.0.items.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'Test Items'],
['name' => 'ds.0.color', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'FF465C']
]
]
]
]
]
]);
self::$disposable_dashboard_id = $response['dashboardids'][0];
}
/**
* Test updating of Pie chart.
*
* @onBefore prepareUpdatePieChart
* @dataProvider getPieChartData
*/
public function testDashboardPieChartWidget_Update($data){
$this->createUpdatePieChart($data, 'Edit widget');
}
/**
* Test opening Pie chart form and saving with no changes made.
*/
public function testDashboardPieChartWidget_SimpleUpdate() {
$old_hash = CDBHelper::getHash(self::SQL);
$dashboard = $this->openDashboard(LOGIN, self::PAGE_2);
$old_widget_count = $dashboard->getWidgets()->count();
$form = $dashboard->edit()->getWidget('Pie chart for simple update')->edit();
$form->submit();
$dashboard->save();
// Assert that nothing changed.
$dashboard = $this->openDashboard(WITHOUT_LOGIN, self::PAGE_2);
$this->assertEquals($old_widget_count, $dashboard->getWidgets()->count());
$this->assertEquals($old_hash, CDBHelper::getHash(self::SQL));
}
/**
* Test data set cloning.
*/
public function testDashboardPieChartWidget_CloneDataSets() {
$dashboard = $this->openDashboard();
$form = $dashboard->edit()->addWidget()->asForm();
// Data to input.
$fields = [
'main_fields' => [
'Name' => 'Pie chart for data set cloning'
],
'Data set' => [
[
'host' => ['Host*', 'one', 'two'],
'item' => ['Item*', 'one', 'two'],
'color' => '00BCD4',
'Aggregation function' => 'min',
'Data set aggregation' => 'not used',
'Data set label' => 'Label 1'
],
[
'type' => self::TYPE_DATA_SET_CLONE
],
[
'type' => self::TYPE_ITEM_LIST,
'host' => self::HOST_NAME_ITEM_LIST,
'Aggregation function' => 'max',
'Data set aggregation' => 'not used',
'Data set label' => 'Label 2',
'items' => [
[
'name' => 'item-1',
'il_color' => '000000'
],
[
'name' => 'item-2'
]
]
],
[
'type' => self::TYPE_DATA_SET_CLONE
]
]
];
$this->fillForm($form, $fields);
$form->submit();
// Transform input data to expected data. The colors are expected to change.
$fields['Data set'][1] = $fields['Data set'][0];
$fields['Data set'][1]['color'] = 'FF465C';
$fields['Data set'][3] = $fields['Data set'][2];
$fields['Data set'][3]['items'][0]['il_color'] = 'FFD54F';
// Assert the result.
$this->assertEditFormAfterSave($dashboard, ['fields' => $fields]);
}
public function getCancelData() {
return [
// Cancel creation, save dashboard.
[
[
'save_widget' => false,
'save_dashboard' => true
]
],
// Create the widget, cancel saving the dashboard.
[
[
'save_widget' => true,
'save_dashboard' => false
]
],
// Cancel update, save dashboard.
[
[
'update' => true,
'save_widget' => false,
'save_dashboard' => true
]
],
// Update but cancel saving the dashboard.
[
[
'update' => true,
'save_widget' => true,
'save_dashboard' => false
]
]
];
}
/**
* Test cancelling Pie chart creation and update.
*
* @dataProvider getCancelData
*/
public function testDashboardPieChartWidget_Cancel($data) {
// Get DB hash and widget count.
$old_hash = CDBHelper::getHash(self::SQL);
$dashboard = $this->openDashboard(LOGIN, self::PAGE_2);
$old_widget_count = $dashboard->getWidgets()->count();
// Edit data in widget's form.
$form = CTestArrayHelper::get($data, 'update')
? $dashboard->edit()->getWidget('Pie chart for cancel')->edit()
: $dashboard->edit()->addWidget()->asForm();
$fields = [
'main_fields' => [
'Name' => 'This name should not be saved',
'Show header' => false,
'Refresh interval' => '10 seconds'
],
'Data set' => [
[
'host' => ['Cancel host'],
'item' => ['Cancel items'],
'Aggregation function' => 'min',
'Data set label' => 'Cancel label'
]
],
'Displaying options' => [
'Draw' => 'Doughnut'
]
];
$this->fillForm($form, $fields);
// Save the widget OR cancel.
if (CTestArrayHelper::get($data, 'save_widget')) {
$form->submit();
}
else {
COverlayDialogElement::find()->one()->query('button:Cancel')->one()->click();
}
// Save the dashboard if needed.
if (CTestArrayHelper::get($data, 'save_dashboard')) {
$dashboard->save();
}
$this->page->open('zabbix.php?action=dashboard.view&dashboardid='.self::$dashboard_id);
// TODO: temporarily commented out due webdriver issue #351858989, alert is not displayed while leaving page during test execution
// Check that alert is present in case of saving the widget without saving the dashboard.
// if ($data['save_widget'] == true && $data['save_dashboard'] == false) {
// $this->page->acceptAlert();
// }
$this->page->waitUntilReady();
$dashboard->selectPage(self::PAGE_2);
// Assert that widget count and DB data has not changed.
$this->assertEquals($old_widget_count, $dashboard->getWidgets()->count());
$this->assertEquals($old_hash, CDBHelper::getHash(self::SQL));
}
/**
* Test the deletion of the Pie chart widget.
*/
public function testDashboardPieChartWidget_Delete(){
$widget_name = 'Pie chart for delete';
$widget_id = CDBHelper::getValue('SELECT widgetid FROM widget WHERE name='.zbx_dbstr($widget_name));
// Delete the widget and save the dashboard.
$dashboard = $this->openDashboard(LOGIN, self::PAGE_2);
$old_widget_count = $dashboard->getWidgets()->count();
$dashboard->edit()->deleteWidget($widget_name)->save();
$this->page->waitUntilReady();
$this->assertMessage(TEST_GOOD, 'Dashboard updated');
// Assert that the widget has been deleted.
$this->assertEquals($old_widget_count - 1, $dashboard->getWidgets()->count());
$this->assertFalse($dashboard->getWidget($widget_name, false)->isValid());
// Check DB tables widget and widget_field.
$sql = 'SELECT NULL FROM widget w CROSS JOIN widget_field wf'.
' WHERE w.widgetid='.zbx_dbstr($widget_id).' OR wf.widgetid='.zbx_dbstr($widget_id);
$this->assertEquals(0, CDBHelper::getCount($sql));
$this->assertEquals(0, CDBHelper::getCount('SELECT null FROM widget WHERE name='.zbx_dbstr($widget_name)));
}
/**
* Prepare a widget for the Pie chart display scenario.
*/
public function preparePieChartDisplayData() {
$providedData = $this->getProvidedData();
$data = reset($providedData);
CDataHelper::removeItemData(array_values(self::$item_ids));
// Minus 10 seconds for safety.
$time = time() - 10;
// Set item history.
foreach($data['item_data'] as $key => $value) {
CDataHelper::addItemData(self::$item_ids[$key], $value, $time);
}
// Fill in Item ids (this only applies to Item list data sets).
foreach ($data['widget_fields'] as $id => $field) {
if (preg_match('/^ds\.[0-9]\.itemids\.[0-9]$/', $field['name'])) {
$field['value'] = self::$item_ids[$field['value']];
$data['widget_fields'][$id] = $field;
}
}
// Set the disposable dashboard to contain the needed widget.
$response = CDataHelper::call('dashboard.create', [
'name' => 'Pie chart dashboard '.md5(serialize($data)),
'pages' => [
[
'widgets' => [
[
'name' => $data['widget_name'],
'view_mode' => CTestArrayHelper::get($data, 'view_mode', 0),
'type' => 'piechart',
'width' => 24,
'height' => 6,
'fields' => $data['widget_fields']
]
]
]
]
]);
self::$disposable_dashboard_id = $response['dashboardids'][0];
}
public function getPieChartDisplayData() {
return [
// Empty Pie chart (no items or data).
[
[
'widget_name' => 'no-data',
'widget_fields' => [
['name' => 'ds.0.hosts.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => self::HOST_NAME_SCREENSHOTS],
['name' => 'ds.0.items.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'item-1']
],
'item_data' => []
]
],
// One item, custom data set name.
[
[
'widget_name' => 'one-item',
'widget_fields' => [
['name' => 'ds.0.hosts.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => self::HOST_NAME_SCREENSHOTS],
['name' => 'ds.0.items.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'item-1'],
['name' => 'ds.0.data_set_label', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'TEST SET ☺'],
['name' => 'ds.0.dataset_aggregation', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 2]
],
'item_data' => [
'item-1' => 99
],
'expected_dataset_name' => 'TEST SET ☺',
'expected_sectors' => [
'item-1' => ['value' => '99', 'color' => '255, 70, 92']
]
]
],
// Two items, data set aggregate function, very small item values.
[
[
'widget_name' => 'two-items',
'widget_fields' => [
['name' => 'ds.0.hosts.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => self::HOST_NAME_SCREENSHOTS],
['name' => 'ds.0.items.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'item-3'],
['name' => 'ds.0.items.1', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'item-4'],
['name' => 'ds.0.aggregate_function', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 2],
['name' => 'legend_aggregation', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1]
],
'item_data' => [
'item-3' => 0.00000000000000003,
'item-4' => 0.00000000000000002
],
'expected_legend_function' => 'max',
'expected_sectors' => [
'item-3' => ['value' => '3E-17', 'color' => '255, 70, 92'],
'item-4' => ['value' => '2E-17', 'color' => '255, 197, 219']
]
]
],
// Five items, host and item pattern, custom colors, hide legend and header.
[
[
'widget_name' => 'five-items',
'view_mode' => 1,
'widget_fields' => [
['name' => 'ds.0.hosts.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'pie-chart-display'],
['name' => 'ds.0.items.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'item-*'],
['name' => 'ds.0.color', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'FFA726'],
['name' => 'legend', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0]
],
'item_data' => [
'item-1' => 1,
'item-2' => 2,
'item-3' => 3.0,
'item-4' => 4.4,
'item-5' => 5.55
],
'expected_sectors' => [
'item-1' => ['value' => '1', 'color' => '127, 39, 0'],
'item-2' => ['value' => '2', 'color' => '178, 90, 0'],
'item-3' => ['value' => '3', 'color' => '229, 141, 12'],
'item-4' => ['value' => '4.4', 'color' => '255, 192, 63'],
'item-5' => ['value' => '5.55', 'color' => '255, 243, 114']
]
]
],
// Doughnut, data set type 'Item list', Total item, merging enabled, total value display, custom legends.
[
[
'widget_name' => 'doughnut',
'widget_fields' => [
// Items and their colors.
['name' => 'ds.0.dataset_type', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0],
['name' => 'ds.0.itemids.0', 'type' => ZBX_WIDGET_FIELD_TYPE_ITEM, 'value' => 'item-1'],
['name' => 'ds.0.type.0', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'ds.0.color.0', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'FF002F'],
['name' => 'ds.0.itemids.1', 'type' => ZBX_WIDGET_FIELD_TYPE_ITEM, 'value' => 'item-2'],
['name' => 'ds.0.type.1', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0],
['name' => 'ds.0.color.1', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'FF4265'],
['name' => 'ds.0.itemids.2', 'type' => ZBX_WIDGET_FIELD_TYPE_ITEM, 'value' => 'item-3'],
['name' => 'ds.0.type.2', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0],
['name' => 'ds.0.color.2', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => 'FA6984'],
['name' => 'ds.0.itemids.3', 'type' => ZBX_WIDGET_FIELD_TYPE_ITEM, 'value' => 'item-4'],
['name' => 'ds.0.type.3', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0],
['name' => 'ds.0.color.3', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => '525252'],
['name' => 'ds.0.itemids.4', 'type' => ZBX_WIDGET_FIELD_TYPE_ITEM, 'value' => 'item-5'],
['name' => 'ds.0.type.4', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0],
['name' => 'ds.0.color.4', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => '525252'],
// Drawing and total value options.
['name' => 'draw_type', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'width', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 50],
['name' => 'total_show', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'value_size_type', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'value_size', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 25],
['name' => 'decimal_places', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 0],
['name' => 'units_show', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'units', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => '♥'],
['name' => 'value_bold', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'value_color', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => '7CB342'],
['name' => 'stroke', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 8],
['name' => 'space', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 6],
['name' => 'merge', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'merge_percent', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 10],
['name' => 'merge_color', 'type' => ZBX_WIDGET_FIELD_TYPE_STR, 'value' => '69B4FA'],
// Legend with values.
['name' => 'legend_value', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'legend_lines_mode', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 1],
['name' => 'legend_lines', 'type' => ZBX_WIDGET_FIELD_TYPE_INT32, 'value' => 2]
],
'item_data' => [
'item-1' => 100,
'item-2' => 25,
'item-3' => 35,
'item-4' => 4,
'item-5' => 5
],
'expected_total' => '100 ♥',
'expected_sectors' => [
'item-1' => ['value' => '100 ♥', 'color' => '255, 0, 47'],
'item-2' => ['value' => '25 ♥', 'color' => '255, 66, 101'],
'item-3' => ['value' => '35 ♥', 'color' => '250, 105, 132'],
'Other' => ['value' => '9 ♥', 'color' => '105, 180, 250']
]
]
]
];
}
/**
* Generate different Pie charts and assert their display.
*
* @onBefore preparePieChartDisplayData
* @dataProvider getPieChartDisplayData
*/
public function testDashboardPieChartWidget_PieChartDisplay($data) {
$this->openDashboard(LOGIN, DEFAULT_PAGE, self::$disposable_dashboard_id);
$widget = CDashboardElement::find()->one()->getWidget($data['widget_name']);
// Wait for the sector animation to end.
sleep(1);
// Assert Pie chart sectors.
$expected_sectors = CTestArrayHelper::get($data, 'expected_sectors', []);
foreach ($expected_sectors as $item_name => $expected_sector) {
// The name shown in the legend and in the hintbox.
$legend_name = self::HOST_NAME_SCREENSHOTS.': '.$item_name;
// Special case - legend name includes aggregation function.
if (CTestArrayHelper::get($data, 'expected_legend_function')) {
$legend_name = CTestArrayHelper::get($data, 'expected_legend_function').'('.$legend_name.')';
}
// Special case - legend name is 'Other' because sectors were merged.
if ($item_name === 'Other') {
$legend_name = 'Other';
}
// Special case - custom legend name.
$legend_name = CTestArrayHelper::get($data, 'expected_dataset_name', $legend_name);
// Locate sector for checking.
$sector = $widget->query('xpath:.//*[contains(@data-hintbox-contents, '.
CXPathHelper::escapeQuotes($legend_name).')]/*[@class="svg-pie-chart-arc"]')->one();
// Assert sector fill color.
$this->assertEquals('rgb('.$expected_sector['color'].')', $sector->getCSSValue('fill'));
// Open and assert the hintbox.
$sector->click();
$hintbox = $this->query('class:overlay-dialogue')->asOverlayDialog()->waitUntilReady()->all()->last();
$this->assertEquals($legend_name.': '."\n".$expected_sector['value'], $hintbox->getText());
$this->assertEquals('rgba('.$expected_sector['color'].', 1)',
$hintbox->query('class:svg-pie-chart-hintbox-color')->one()->getCSSValue('background-color')
);
$hintbox->close();
}
// Assert the total count of sectors.
$this->assertEquals(count($expected_sectors), $widget->query('class:svg-pie-chart-arc')->all()->count());
// Assert expected Total value.
if (CTestArrayHelper::get($data, 'expected_total')) {
$this->assertEquals($data['expected_total'], $widget->query('class:svg-pie-chart-total-value')->one()->getText());
}
// Make sure none of the sectors are hovered.
$this->query('id:page-title-general')->one()->hoverMouse();
// Wait for the sector hover animation to end before taking a screenshot.
sleep(1);
// Screenshot the widget.
$this->page->removeFocus();
$this->assertScreenshot($widget, $data['widget_name']);
}
/**
* Tests that only the correct item types can be used in the Pie chart widget.
*/
public function testDashboardPieChartWidget_CheckAvailableItems() {
$this->checkAvailableItems('zabbix.php?action=dashboard.view&dashboardid='.self::$dashboard_id, 'Pie chart');
}
/**
* Creates or updates a widget according to data from data provider.
*
* @param array $data data from data provider
* @param string $update_widget_name if this is set, then a widget named like this is updated
*/
protected function createUpdatePieChart($data, $update_widget_name = null) {
$dashboard_id = $update_widget_name ? self::$disposable_dashboard_id : self::$dashboard_id;
$dashboard = $this->openDashboard(LOGIN, DEFAULT_PAGE, $dashboard_id);
$old_widget_count = $dashboard->getWidgets()->count();
$form = $update_widget_name
? $dashboard->edit()->getWidget($update_widget_name)->edit()
: $dashboard->edit()->addWidget()->asForm();
$this->fillForm($form, $data['fields'], $update_widget_name !== null);
$form->submit();
// Assert the result.
$this->assertEditFormAfterSave($dashboard, $data, isset($update_widget_name));
// Check total Widget count.
$count_added = (!$update_widget_name && CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_GOOD) ? 1 : 0;
$this->assertEquals($old_widget_count + $count_added, $dashboard->getWidgets()->count());
}
/**
* Checks the hintboxes in the Data set tab.
*
* @param CFormElement $form data set form
*/
protected function validateDataSetHintboxes($form) {
$hints = [
'Aggregation function' => 'Aggregates each item in the data set.',
'Data set aggregation' => 'Aggregates the whole data set.',
'Data set label' => 'Also used as legend label for aggregated data sets.'
];
// For each hintbox - open, assert text, close.
foreach ($hints as $field => $text) {
$form->getLabel($field)->query('xpath:./button[@data-hintbox]')->one()->waitUntilClickable()->click();
$hint = $this->query('xpath://div[@class="overlay-dialogue wordbreak"]')->asOverlayDialog()->waitUntilPresent()->one();
$this->assertEquals($text, $hint->getText());
$hint->query('xpath:./button')->one()->click();
}
}
/**
* Asserts range input attributes.
*
* @param CFormElement $form parent form
* @param string $label label of the range input
* @param array $expected_values the attribute values expected
*/
protected function assertRangeSliderParameters($form, $label, $expected_values) {
$range = $form->getField($label)->query('xpath:.//input[@type="range"]')->one();
foreach ($expected_values as $attribute => $expected_value) {
$this->assertEquals($expected_value, $range->getAttribute($attribute));
}
}
/**
* Asserts that data is saved and displayed as expected in the edit form.
*
* @param CDashboardElement $dashboard dashboard element
* @param array $data data from data provider
* @param bool $update add additional string to the 'Name' field, so it is unique
*/
protected function assertEditFormAfterSave($dashboard, $data, $update = null) {
$widget_name = CTestArrayHelper::get($data['fields'], 'main_fields.Name', md5(serialize($data['fields'])));
$widget_name = $update ? $widget_name.' updated' : $widget_name;
$count_sql = 'SELECT NULL FROM widget WHERE name='.zbx_dbstr($widget_name);
if (CTestArrayHelper::get($data, 'expected', TEST_GOOD) === TEST_GOOD) {
COverlayDialogElement::ensureNotPresent();
// Save Dashboard.
$widget = $dashboard->getWidget($widget_name);
$dashboard->save();
$this->assertMessage(TEST_GOOD, 'Dashboard updated');
// Assert data in edit form.
$form = $widget->edit();
// Check main fields
$main_fields = CTestArrayHelper::get($data['fields'], 'main_fields', []);
$main_fields['Name'] = $widget_name;
$form->checkValue($main_fields);
// Get expected data set data.
$expected_data_sets = $this->extractDataSets($data['fields']);
$last_data_set = count($expected_data_sets) - 1;
// For each expected data set.
foreach ($expected_data_sets as $i => $data_set) {
$type = CTestArrayHelper::get($data_set, 'type', self::TYPE_ITEM_PATTERN);
// There is no actual 'Type' field in the form.
unset($data_set['type']);
// Additional steps for Item list.
if ($type === self::TYPE_ITEM_LIST) {
// Check Host and all Items.
foreach ($data_set['items'] as $item) {
$this->assertTrue($form->query('link', $data_set['host'].': '.$item['name'])->exists());
}
// There is no 'Host' field for Item lists.
unset($data_set['host']);
}
// Check data set form.
$data_set = $this->remapDataSet($data_set, $i);
$form->checkValue($data_set);
// Check data set label.
$label = CTestArrayHelper::get($data_set, 'Data set label', 'Data set #'.$i + 1);
$this->assertEquals($label,
$form->query('xpath:.//li[@data-set="'.$i.'"]//label[contains(@class, "js-dataset-label")]')->
one()->getText()
);
// Open the next data set, if it exists.
if ($i !== $last_data_set) {
$form->query('xpath:.//li[contains(@class, "list-accordion-item")]['.
($i + 2).']//button[contains(@class, "list-accordion-item-toggle")]')->one()->click();
$form->invalidate();
}
}
// Check forms of other tabs.
$tabs = ['Displaying options', 'Time period', 'Legend'];
foreach ($tabs as $tab) {
if (array_key_exists($tab, $data['fields'])) {
$form->selectTab($tab);
$form->checkValue($data['fields'][$tab]);
}
}
// Assert that DB record exists.
$this->assertEquals(1, CDBHelper::getCount($count_sql));
}
else {
// When error expected.
$this->assertMessage(TEST_BAD, null, $data['error']);
$this->assertEquals(0, CDBHelper::getCount($count_sql));
}
COverlayDialogElement::find()->one()->close();
}
/**
* Fill Pie chart widget edit form with provided data.
*
* @param CFormElement $form form to be filled
* @param array $fields field data to fill
* @param bool $update add additional string to the 'Name' field, so it is unique
*/
protected function fillForm($form, $fields, $update = false) {
// Fill main fields.
$main_fields = CTestArrayHelper::get($fields, 'main_fields', []);
$main_fields['Name'] = CTestArrayHelper::get($fields, 'main_fields.Name', md5(serialize($fields)));
$main_fields['Name'] = $update ? $main_fields['Name'].' updated' : $main_fields['Name'];
$form->fill($main_fields);
// Fill datasets.
$delete = CTestArrayHelper::get($fields, 'delete_data_set');
$remake = CTestArrayHelper::get($fields, 'remake_data_set');
if ($delete || $remake) {
$form->query('xpath:.//button[@title="Delete"]')->one()->click();
if ($remake) {
$form->query('button:Add new data set')->one()->click();
$form->invalidate();
}
}
if (!$delete) {
$this->fillDatasets($form, $this->extractDataSets($fields));
}
// Fill the other tabs.
foreach (['Displaying options', 'Time period', 'Legend'] as $tab) {
if (array_key_exists($tab, $fields)) {
$form->selectTab($tab);
$form->fill($fields[$tab]);
}
}
}
/**
* Fill 'Data sets' tab with field data.
*
* @param CFormElement $form CFormElement to be filled
* @param array $data_sets array of data sets to be filled
*/
protected function fillDatasets($form, $data_sets) {
// Count of data sets that already exist (needed for updating).
$count_sets = $form->query('xpath:.//li[contains(@class, "list-accordion-item")]')->all()->count();
// For each data set to fill.
foreach ($data_sets as $i => $data_set) {
$type = CTestArrayHelper::get($data_set, 'type', self::TYPE_ITEM_PATTERN);
unset($data_set['type']);
// Special case: the first Data set is of type Item list.
$deleted_first_set = false;
if ($i === 0 && $type === self::TYPE_ITEM_LIST && $count_sets === 1) {
$form->query('xpath:.//button[@title="Delete"]')->one()->click();
$deleted_first_set = true;
}
// Open the Data set or create a new one.
if ($i + 1 < $count_sets) {
$form->query('xpath:.//li[contains(@class, "list-accordion-item")]['.
($i + 1).']//button[contains(@class, "list-accordion-item-toggle")]')->one()->click();
}
else if ($i !== 0 || $deleted_first_set) {
// Only add a new Data set if it is not the first one or the first one was deleted.
$this->addNewDataSet($form, $type);
}
$form->invalidate();
// Need additional steps when Data set type is Item list, but only if Host is set at all.
if ($type === self::TYPE_ITEM_LIST && array_key_exists('host', $data_set)) {
// Select Host.
$form->query('button:Add item')->one()->click();
$dialog = COverlayDialogElement::find()->all()->last()->waitUntilReady();
$select = $dialog->query('class:multiselect-control')->asMultiselect()->one();
$select->fill($data_set['host']);
unset($data_set['host']);
// Select Items.
$table = $dialog->query('class:list-table')->waitUntilVisible()->asTable()->one();
foreach ($data_set['items'] as $item) {
// Get the correct checkbox in table by knowing only link text;
$checkbox_xpath = 'xpath:.//a[text()='.CXPathHelper::escapeQuotes($item['name']).
']/../../preceding-sibling::td/input[@type="checkbox"]';
$table->query($checkbox_xpath)->waitUntilPresent()->asCheckbox()->one()->check();
}
$dialog->getFooter()->query('button:Select')->one()->click();
$dialog->waitUntilNotVisible();
}
$data_set = $this->remapDataSet($data_set, $i);
$form->fill($data_set);
}
}
/**
* Clicks the correct 'Add new data set' button.
*
* @param CFormElement $form widget edit form
* @param string $type type of data set
* @param boolean $button for 'Item pattern' only: use 'Add new data set' button if true, or select from context menu if false
*/
protected function addNewDataSet($form, $type = null, $button = true) {
if (($type === self::TYPE_ITEM_PATTERN || $type === null) && $button) {
$form->query('button:Add new data set')->one()->click();
}
else {
$this->query('id:dataset-menu')->asPopupButton()->one()->select($type);
}
}
/**
* Exchanges generic field names for the actual field selectors in a Data set form.
*
* @param array $data_set Data set data
* @param int $number the position of this data set in UI
*
* @return array remapped Data set
*/
protected function remapDataSet($data_set, $number) {
// Simple selector to the actual selector mapping.
$mapping = [
'host' => 'xpath:.//div[@id="ds_'.$number.'_hosts_"]/..',
'item' => 'xpath:.//div[@id="ds_'.$number.'_items_"]/..',
'color' => 'xpath:.//input[@id="ds_'.$number.'_color"]/..',
'il_color' => 'xpath:.//input[@id="items_'.$number.'_{id}_color"]/..',
'il_type' => 'xpath:.//z-select[@id="items_'.$number.'_{id}_type"]'
];
// Exchange the keys for the actual selectors and clear the old key.
foreach ($data_set as $data_set_key => $data_set_value) {
// Only change mapped selectors.
if (array_key_exists($data_set_key, $mapping)) {
$data_set += [$mapping[$data_set_key] => $data_set_value];
unset($data_set[$data_set_key]);
}
}
// Also map item fields for Item list.
if (array_key_exists('items', $data_set)) {
// An Item list can have several items.
foreach ($data_set['items'] as $item_id => $item) {
// An item can have several fields.
foreach ($item as $field_key => $field_value) {
// Only change mapped selectors.
if (array_key_exists($field_key, $mapping)) {
// Set the item ID in selector, it starts at 1.
$mapped_value = str_replace('{id}', $item_id + 1, $mapping[$field_key]);
$data_set += [$mapped_value => $field_value];
}
}
}
unset($data_set['items']);
}
return $data_set;
}
/**
* Takes field data from a data provider and sets the defaults for Data sets.
*
* @param array $fields field data from data provider
*
* @return array field data with default values set
*/
protected function extractDataSets($fields) {
// Add one default data set if none defined.
$data_sets = array_key_exists('Data set', $fields)
? $fields['Data set']
: ['host' => 'Test Host', 'item' => 'Test Item'];
// Set as an array of data sets if needed.
if (CTestArrayHelper::isAssociative($data_sets)) {
$data_sets = [$data_sets];
}
// Set default values for any empty data sets.
foreach ($data_sets as $i => $data_set) {
if ($data_set === []) {
$data_sets[$i] = ['host' => 'Test Host '.$i, 'item' => 'Test Item '.$i];
}
}
return $data_sets;
}
/**
* Checks HTML attributes of a field.
*
* @param CFormElement $form form element of the field
* @param string $name name (or selector) of the field
* @param array $attributes the expected attributes
* @param bool $find_in_children true if the needed input field is actually a child of the form field element
*/
protected function assertFieldAttributes($form, $name, $attributes, $find_in_children = false) {
$input = $form->getField($name);
if ($find_in_children) {
$input = $input->query('tag:input')->one();
}
foreach ($attributes as $attribute => $expected_value) {
$this->assertEquals($expected_value, $input->getAttribute($attribute));
}
}
/**
* Opens the Pie chart dashboard.
*
* @param bool $login skips logging in if set to false
* @param string $page opens a page of this name if set
* @param int $dashboard_id opens dashboard with this id
*
* @return CDashboardElement dashboard element of the Pie chart dashboard
*/
protected function openDashboard($login = true, $page = null, $dashboard_id = null) {
if ($login) {
$this->page->login();
}
$id = $dashboard_id === null ? self::$dashboard_id : $dashboard_id;
$this->page->open('zabbix.php?action=dashboard.view&dashboardid='.$id)->waitUntilReady();
$dashboard = CDashboardElement::find()->one();
if ($page) {
$dashboard->selectPage($page);
}
return $dashboard;
}
/**
* Checks visible labels inside an element (form). Fails if a label is missing or if there are unexpected labels.
*
* @param CElement $element element to check
* @param array $expected_labels list of all expected visible labels
*/
protected function assertAllVisibleLabels($element, $expected_labels) {
// There are weird labels in this form but at the same time we don't need to match all labels, for example radio buttons.
$label_selector = 'xpath:.//div[@class="form-grid"]/label'. // standard case
' | .//div[@class="form-field"]/label'. // when the label is a child of the field
' | .//label[contains(@class, "js-dataset-label")]'; // this matches data set labels
$actual_labels = $element->query($label_selector)->all()->filter(CElementFilter::VISIBLE)->asText();
// Remove empty labels (checkbox styling is a label) from the list.
$actual_labels = array_filter($actual_labels);
// Make sure expected and actual labels match, but ignore the order.
$this->assertEqualsCanonicalizing($expected_labels, $actual_labels,
'The expected visible labels and the actual visible labels are different.'
);
}
}