89 changed files with 1279 additions and 683 deletions
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* 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. |
|||
*/ |
|||
package org.thingsboard.server.common.data.sync.vc; |
|||
|
|||
import org.apache.commons.lang3.StringUtils; |
|||
|
|||
public class VcUtils { |
|||
|
|||
private VcUtils() {} |
|||
|
|||
public static void checkBranchName(String branch) { |
|||
if (StringUtils.isEmpty(branch)) return; |
|||
|
|||
boolean invalid = StringUtils.containsWhitespace(branch) || |
|||
StringUtils.containsAny(branch, "..", "~", "^", ":", "\\") || |
|||
StringUtils.endsWithAny(branch, "/", ".lock"); |
|||
if (invalid) { |
|||
throw new IllegalArgumentException("Branch name is invalid"); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,282 @@ |
|||
-- |
|||
-- Copyright © 2016-2023 The Thingsboard Authors |
|||
-- |
|||
-- Licensed under the Apache License, Version 2.0 (the "License"); |
|||
-- 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. |
|||
-- |
|||
|
|||
DROP VIEW IF EXISTS device_info_active_attribute_view CASCADE; |
|||
CREATE OR REPLACE VIEW device_info_active_attribute_view AS |
|||
SELECT d.* |
|||
, c.title as customer_title |
|||
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public |
|||
, d.type as device_profile_name |
|||
, COALESCE(da.bool_v, FALSE) as active |
|||
FROM device d |
|||
LEFT JOIN customer c ON c.id = d.customer_id |
|||
LEFT JOIN attribute_kv da ON da.entity_id = d.id and da.attribute_key = 'active'; |
|||
|
|||
DROP VIEW IF EXISTS device_info_active_ts_view CASCADE; |
|||
CREATE OR REPLACE VIEW device_info_active_ts_view AS |
|||
SELECT d.* |
|||
, c.title as customer_title |
|||
, COALESCE((c.additional_info::json->>'isPublic')::bool, FALSE) as customer_is_public |
|||
, d.type as device_profile_name |
|||
, COALESCE(dt.bool_v, FALSE) as active |
|||
FROM device d |
|||
LEFT JOIN customer c ON c.id = d.customer_id |
|||
LEFT JOIN ts_kv_latest dt ON dt.entity_id = d.id and dt.key = (select key_id from ts_kv_dictionary where key = 'active'); |
|||
|
|||
DROP VIEW IF EXISTS device_info_view CASCADE; |
|||
CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view; |
|||
|
|||
DROP VIEW IF EXISTS alarm_info CASCADE; |
|||
CREATE VIEW alarm_info AS |
|||
SELECT a.*, |
|||
(CASE WHEN a.acknowledged AND a.cleared THEN 'CLEARED_ACK' |
|||
WHEN NOT a.acknowledged AND a.cleared THEN 'CLEARED_UNACK' |
|||
WHEN a.acknowledged AND NOT a.cleared THEN 'ACTIVE_ACK' |
|||
WHEN NOT a.acknowledged AND NOT a.cleared THEN 'ACTIVE_UNACK' END) as status, |
|||
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) |
|||
WHEN a.originator_type = 1 THEN (select title from customer where id = a.originator_id) |
|||
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) |
|||
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) |
|||
WHEN a.originator_type = 4 THEN (select name from asset where id = a.originator_id) |
|||
WHEN a.originator_type = 5 THEN (select name from device where id = a.originator_id) |
|||
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) |
|||
WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) |
|||
WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) |
|||
WHEN a.originator_type = 18 THEN (select name from edge where id = a.originator_id) END |
|||
, 'Deleted') originator_name, |
|||
COALESCE(CASE WHEN a.originator_type = 0 THEN (select title from tenant where id = a.originator_id) |
|||
WHEN a.originator_type = 1 THEN (select COALESCE(NULLIF(title, ''), email) from customer where id = a.originator_id) |
|||
WHEN a.originator_type = 2 THEN (select email from tb_user where id = a.originator_id) |
|||
WHEN a.originator_type = 3 THEN (select title from dashboard where id = a.originator_id) |
|||
WHEN a.originator_type = 4 THEN (select COALESCE(NULLIF(label, ''), name) from asset where id = a.originator_id) |
|||
WHEN a.originator_type = 5 THEN (select COALESCE(NULLIF(label, ''), name) from device where id = a.originator_id) |
|||
WHEN a.originator_type = 9 THEN (select name from entity_view where id = a.originator_id) |
|||
WHEN a.originator_type = 13 THEN (select name from device_profile where id = a.originator_id) |
|||
WHEN a.originator_type = 14 THEN (select name from asset_profile where id = a.originator_id) |
|||
WHEN a.originator_type = 18 THEN (select COALESCE(NULLIF(label, ''), name) from edge where id = a.originator_id) END |
|||
, 'Deleted') as originator_label, |
|||
u.first_name as assignee_first_name, u.last_name as assignee_last_name, u.email as assignee_email |
|||
FROM alarm a |
|||
LEFT JOIN tb_user u ON u.id = a.assignee_id; |
|||
|
|||
CREATE OR REPLACE FUNCTION create_or_update_active_alarm( |
|||
t_id uuid, c_id uuid, a_id uuid, a_created_ts bigint, |
|||
a_o_id uuid, a_o_type integer, a_type varchar, |
|||
a_severity varchar, a_start_ts bigint, a_end_ts bigint, |
|||
a_details varchar, |
|||
a_propagate boolean, a_propagate_to_owner boolean, |
|||
a_propagate_to_tenant boolean, a_propagation_types varchar, |
|||
a_creation_enabled boolean) |
|||
RETURNS varchar |
|||
LANGUAGE plpgsql |
|||
AS |
|||
$$ |
|||
DECLARE |
|||
null_id constant uuid = '13814000-1dd2-11b2-8080-808080808080'::uuid; |
|||
existing alarm; |
|||
result alarm_info; |
|||
row_count integer; |
|||
BEGIN |
|||
SELECT * INTO existing FROM alarm a WHERE a.originator_id = a_o_id AND a.type = a_type AND a.cleared = false ORDER BY a.start_ts DESC FOR UPDATE; |
|||
IF existing.id IS NULL THEN |
|||
IF a_creation_enabled = FALSE THEN |
|||
RETURN json_build_object('success', false)::text; |
|||
END IF; |
|||
IF c_id = null_id THEN |
|||
c_id = NULL; |
|||
end if; |
|||
INSERT INTO alarm |
|||
(tenant_id, customer_id, id, created_time, |
|||
originator_id, originator_type, type, |
|||
severity, start_ts, end_ts, |
|||
additional_info, |
|||
propagate, propagate_to_owner, propagate_to_tenant, propagate_relation_types, |
|||
acknowledged, ack_ts, |
|||
cleared, clear_ts, |
|||
assignee_id, assign_ts) |
|||
VALUES |
|||
(t_id, c_id, a_id, a_created_ts, |
|||
a_o_id, a_o_type, a_type, |
|||
a_severity, a_start_ts, a_end_ts, |
|||
a_details, |
|||
a_propagate, a_propagate_to_owner, a_propagate_to_tenant, a_propagation_types, |
|||
false, 0, false, 0, NULL, 0); |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
RETURN json_build_object('success', true, 'created', true, 'modified', true, 'alarm', row_to_json(result))::text; |
|||
ELSE |
|||
UPDATE alarm a |
|||
SET severity = a_severity, |
|||
start_ts = a_start_ts, |
|||
end_ts = a_end_ts, |
|||
additional_info = a_details, |
|||
propagate = a_propagate, |
|||
propagate_to_owner = a_propagate_to_owner, |
|||
propagate_to_tenant = a_propagate_to_tenant, |
|||
propagate_relation_types = a_propagation_types |
|||
WHERE a.id = existing.id |
|||
AND a.tenant_id = t_id |
|||
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details |
|||
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR |
|||
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types); |
|||
GET DIAGNOSTICS row_count = ROW_COUNT; |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = existing.id AND a.tenant_id = t_id; |
|||
IF row_count > 0 THEN |
|||
RETURN json_build_object('success', true, 'modified', true, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text; |
|||
ELSE |
|||
RETURN json_build_object('success', true, 'modified', false, 'alarm', row_to_json(result))::text; |
|||
END IF; |
|||
END IF; |
|||
END |
|||
$$; |
|||
|
|||
DROP FUNCTION IF EXISTS update_alarm; |
|||
CREATE OR REPLACE FUNCTION update_alarm(t_id uuid, a_id uuid, a_severity varchar, a_start_ts bigint, a_end_ts bigint, |
|||
a_details varchar, |
|||
a_propagate boolean, a_propagate_to_owner boolean, |
|||
a_propagate_to_tenant boolean, a_propagation_types varchar) |
|||
RETURNS varchar |
|||
LANGUAGE plpgsql |
|||
AS |
|||
$$ |
|||
DECLARE |
|||
existing alarm; |
|||
result alarm_info; |
|||
row_count integer; |
|||
BEGIN |
|||
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; |
|||
IF existing IS NULL THEN |
|||
RETURN json_build_object('success', false)::text; |
|||
END IF; |
|||
UPDATE alarm a |
|||
SET severity = a_severity, |
|||
start_ts = a_start_ts, |
|||
end_ts = a_end_ts, |
|||
additional_info = a_details, |
|||
propagate = a_propagate, |
|||
propagate_to_owner = a_propagate_to_owner, |
|||
propagate_to_tenant = a_propagate_to_tenant, |
|||
propagate_relation_types = a_propagation_types |
|||
WHERE a.id = a_id |
|||
AND a.tenant_id = t_id |
|||
AND (severity != a_severity OR start_ts != a_start_ts OR end_ts != a_end_ts OR additional_info != a_details |
|||
OR propagate != a_propagate OR propagate_to_owner != a_propagate_to_owner OR |
|||
propagate_to_tenant != a_propagate_to_tenant OR propagate_relation_types != a_propagation_types); |
|||
GET DIAGNOSTICS row_count = ROW_COUNT; |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
IF row_count > 0 THEN |
|||
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text; |
|||
ELSE |
|||
RETURN json_build_object('success', true, 'modified', row_count > 0, 'alarm', row_to_json(result))::text; |
|||
END IF; |
|||
END |
|||
$$; |
|||
|
|||
DROP FUNCTION IF EXISTS acknowledge_alarm; |
|||
CREATE OR REPLACE FUNCTION acknowledge_alarm(t_id uuid, a_id uuid, a_ts bigint) |
|||
RETURNS varchar |
|||
LANGUAGE plpgsql |
|||
AS |
|||
$$ |
|||
DECLARE |
|||
existing alarm; |
|||
result alarm_info; |
|||
modified boolean = FALSE; |
|||
BEGIN |
|||
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; |
|||
IF existing IS NULL THEN |
|||
RETURN json_build_object('success', false)::text; |
|||
END IF; |
|||
|
|||
IF NOT (existing.acknowledged) THEN |
|||
modified = TRUE; |
|||
UPDATE alarm a SET acknowledged = true, ack_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
END IF; |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result), 'old', row_to_json(existing))::text; |
|||
END |
|||
$$; |
|||
|
|||
DROP FUNCTION IF EXISTS clear_alarm; |
|||
CREATE OR REPLACE FUNCTION clear_alarm(t_id uuid, a_id uuid, a_ts bigint, a_details varchar) |
|||
RETURNS varchar |
|||
LANGUAGE plpgsql |
|||
AS |
|||
$$ |
|||
DECLARE |
|||
existing alarm; |
|||
result alarm_info; |
|||
cleared boolean = FALSE; |
|||
BEGIN |
|||
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; |
|||
IF existing IS NULL THEN |
|||
RETURN json_build_object('success', false)::text; |
|||
END IF; |
|||
IF NOT(existing.cleared) THEN |
|||
cleared = TRUE; |
|||
UPDATE alarm a SET cleared = true, clear_ts = a_ts, additional_info = a_details WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
END IF; |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
RETURN json_build_object('success', true, 'cleared', cleared, 'alarm', row_to_json(result))::text; |
|||
END |
|||
$$; |
|||
|
|||
DROP FUNCTION IF EXISTS assign_alarm; |
|||
CREATE OR REPLACE FUNCTION assign_alarm(t_id uuid, a_id uuid, u_id uuid, a_ts bigint) |
|||
RETURNS varchar |
|||
LANGUAGE plpgsql |
|||
AS |
|||
$$ |
|||
DECLARE |
|||
existing alarm; |
|||
result alarm_info; |
|||
modified boolean = FALSE; |
|||
BEGIN |
|||
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; |
|||
IF existing IS NULL THEN |
|||
RETURN json_build_object('success', false)::text; |
|||
END IF; |
|||
IF existing.assignee_id IS NULL OR existing.assignee_id != u_id THEN |
|||
modified = TRUE; |
|||
UPDATE alarm a SET assignee_id = u_id, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
END IF; |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text; |
|||
END |
|||
$$; |
|||
|
|||
DROP FUNCTION IF EXISTS unassign_alarm; |
|||
CREATE OR REPLACE FUNCTION unassign_alarm(t_id uuid, a_id uuid, a_ts bigint) |
|||
RETURNS varchar |
|||
LANGUAGE plpgsql |
|||
AS |
|||
$$ |
|||
DECLARE |
|||
existing alarm; |
|||
result alarm_info; |
|||
modified boolean = FALSE; |
|||
BEGIN |
|||
SELECT * INTO existing FROM alarm a WHERE a.id = a_id AND a.tenant_id = t_id FOR UPDATE; |
|||
IF existing IS NULL THEN |
|||
RETURN json_build_object('success', false)::text; |
|||
END IF; |
|||
IF existing.assignee_id IS NOT NULL THEN |
|||
modified = TRUE; |
|||
UPDATE alarm a SET assignee_id = NULL, assign_ts = a_ts WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
END IF; |
|||
SELECT * INTO result FROM alarm_info a WHERE a.id = a_id AND a.tenant_id = t_id; |
|||
RETURN json_build_object('success', true, 'modified', modified, 'alarm', row_to_json(result))::text; |
|||
END |
|||
$$; |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* 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. |
|||
*/ |
|||
package org.thingsboard.server.msa.ui.pages; |
|||
|
|||
import org.openqa.selenium.WebDriver; |
|||
import org.openqa.selenium.WebElement; |
|||
|
|||
public class AlarmWidgetElements extends AlarmDetailsEntityTabHelper { |
|||
public AlarmWidgetElements(WebDriver driver) { |
|||
super(driver); |
|||
} |
|||
|
|||
private static final String ASSIGN_USER_DISPLAY_NAME = "//span[contains(@class,'assigned-container')]/span[contains(text(),'%s')]"; |
|||
private static final String UNASSIGNED = "//span[text() = '%s']/ancestor::mat-row//span[contains(@class,'assigned-container')]" + |
|||
"//mat-icon[text() = 'account_circle']/following-sibling::span"; |
|||
|
|||
@Override |
|||
public WebElement assignedUser(String userEmail) { |
|||
return waitUntilElementToBeClickable(String.format(ASSIGN_USER_DISPLAY_NAME, userEmail)); |
|||
} |
|||
|
|||
@Override |
|||
public WebElement unassigned(String alarmType) { |
|||
return waitUntilVisibilityOfElementLocated(String.format(UNASSIGNED, alarmType)); |
|||
} |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* 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. |
|||
*/ |
|||
package org.thingsboard.server.msa.ui.pages; |
|||
|
|||
import org.openqa.selenium.WebDriver; |
|||
import org.openqa.selenium.WebElement; |
|||
import org.thingsboard.server.msa.ui.base.AbstractBasePage; |
|||
|
|||
public class CreateWidgetPopupElements extends AbstractBasePage { |
|||
public CreateWidgetPopupElements(WebDriver driver) { |
|||
super(driver); |
|||
} |
|||
|
|||
private static final String ENTITY_ALIAS = "//input[@formcontrolname='entityAlias']"; |
|||
private static final String CREATE_NEW_ALIAS_BTN = "//a[text() = 'Create a new one!']/parent::span"; |
|||
private static final String FILTER_TYPE_FIELD = "//div[contains(@class,'tb-entity-filter')]//mat-select//span"; |
|||
private static final String TYPE_FIELD = "//mat-select[@formcontrolname='entityType']//span"; |
|||
private static final String OPTION_FROM_DROPDOWN = "//span[text() = ' %s ']"; |
|||
private static final String ENTITY_FIELD = "//input[@formcontrolname='entity']"; |
|||
private static final String ADD_ALIAS_BTN = "//tb-entity-alias-dialog//span[text() = ' Add ']/parent::button"; |
|||
private static final String ADD_WIDGET_BTN = "//tb-add-widget-dialog//span[text() = ' Add ']/parent::button"; |
|||
private static final String ENTITY_FROM_DROPDOWN = "//b[text() = '%s']"; |
|||
|
|||
public WebElement entityAlias() { |
|||
return waitUntilElementToBeClickable(ENTITY_ALIAS); |
|||
} |
|||
|
|||
public WebElement createNewAliasBtn() { |
|||
return waitUntilElementToBeClickable(CREATE_NEW_ALIAS_BTN); |
|||
} |
|||
|
|||
public WebElement filterTypeFiled() { |
|||
return waitUntilElementToBeClickable(FILTER_TYPE_FIELD); |
|||
} |
|||
|
|||
public WebElement typeFiled() { |
|||
return waitUntilElementToBeClickable(TYPE_FIELD); |
|||
} |
|||
|
|||
public WebElement optionFromDropdown(String type) { |
|||
return waitUntilElementToBeClickable(String.format(OPTION_FROM_DROPDOWN, type)); |
|||
} |
|||
|
|||
public WebElement entityFiled() { |
|||
return waitUntilElementToBeClickable(ENTITY_FIELD); |
|||
} |
|||
|
|||
public WebElement addAliasBtn() { |
|||
return waitUntilElementToBeClickable(ADD_ALIAS_BTN); |
|||
} |
|||
|
|||
public WebElement addWidgetBtn() { |
|||
return waitUntilElementToBeClickable(ADD_WIDGET_BTN); |
|||
} |
|||
|
|||
public WebElement entityFromDropdown(String entityName) { |
|||
return waitUntilVisibilityOfElementLocated(String.format(ENTITY_FROM_DROPDOWN, entityName)); |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* 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. |
|||
*/ |
|||
package org.thingsboard.server.msa.ui.pages; |
|||
|
|||
import org.openqa.selenium.Keys; |
|||
import org.openqa.selenium.WebDriver; |
|||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; |
|||
|
|||
public class CreateWidgetPopupHelper extends CreateWidgetPopupElements { |
|||
public CreateWidgetPopupHelper(WebDriver driver) { |
|||
super(driver); |
|||
} |
|||
|
|||
public void goToCreateEntityAliasPopup(String aliasName) { |
|||
entityAlias().sendKeys(aliasName + RandomStringUtils.randomAlphanumeric(7)); |
|||
createNewAliasBtn().click(); |
|||
} |
|||
|
|||
public void selectFilterType(String filterType) { |
|||
filterTypeFiled().click(); |
|||
optionFromDropdown(filterType).click(); |
|||
} |
|||
|
|||
public void selectType(String entityType) { |
|||
typeFiled().click(); |
|||
optionFromDropdown(entityType).click(); |
|||
} |
|||
|
|||
public void selectEntity(String entityName) { |
|||
entityFiled().sendKeys(entityName); |
|||
entityFromDropdown(entityName); |
|||
entityFiled().sendKeys(Keys.ARROW_DOWN); |
|||
entityFiled().sendKeys(Keys.ENTER); |
|||
} |
|||
} |
|||
@ -0,0 +1,146 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* 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. |
|||
*/ |
|||
package org.thingsboard.server.msa.ui.tests.assignee; |
|||
|
|||
import io.qameta.allure.Description; |
|||
import io.qameta.allure.Feature; |
|||
import org.testng.annotations.AfterClass; |
|||
import org.testng.annotations.BeforeClass; |
|||
import org.testng.annotations.BeforeMethod; |
|||
import org.testng.annotations.Test; |
|||
import org.thingsboard.server.common.data.Dashboard; |
|||
import org.thingsboard.server.msa.ui.pages.AlarmWidgetElements; |
|||
import org.thingsboard.server.msa.ui.pages.CreateWidgetPopupHelper; |
|||
import org.thingsboard.server.msa.ui.pages.DashboardPageHelper; |
|||
import org.thingsboard.server.msa.ui.utils.Const; |
|||
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
@Feature("Assign from details tab of entity") |
|||
public class AssignFromAlarmWidgetTest extends AbstractAssignTest { |
|||
|
|||
private Dashboard dashboard; |
|||
private DashboardPageHelper dashboardPage; |
|||
private AlarmWidgetElements alarmWidget; |
|||
|
|||
@BeforeClass |
|||
public void create() { |
|||
dashboardPage = new DashboardPageHelper(driver); |
|||
CreateWidgetPopupHelper createWidgetPopup = new CreateWidgetPopupHelper(driver); |
|||
alarmWidget = new AlarmWidgetElements(driver); |
|||
|
|||
dashboard = testRestClient.postDashboard(EntityPrototypes.defaultDashboardPrototype("Dashboard")); |
|||
sideBarMenuView.dashboardBtn().click(); |
|||
dashboardPage.entity(dashboard.getName()).click(); |
|||
dashboardPage.editBtn().click(); |
|||
dashboardPage.openSelectWidgetsBundleMenu(); |
|||
dashboardPage.openCreateWidgetPopup(); |
|||
createWidgetPopup.goToCreateEntityAliasPopup("Alias"); |
|||
createWidgetPopup.selectFilterType("Single entity"); |
|||
createWidgetPopup.selectType("Device"); |
|||
createWidgetPopup.selectEntity(deviceName); |
|||
createWidgetPopup.addAliasBtn().click(); |
|||
createWidgetPopup.addWidgetBtn().click(); |
|||
dashboardPage.increaseSizeOfTheWidget(); |
|||
dashboardPage.doneBtn().click(); |
|||
} |
|||
|
|||
@AfterClass |
|||
public void delete() { |
|||
deleteDashboardById(dashboard.getId()); |
|||
} |
|||
|
|||
@BeforeMethod |
|||
public void goToDashboardPage() { |
|||
sideBarMenuView.dashboardBtn().click(); |
|||
dashboardPage.entity(dashboard.getName()).click(); |
|||
} |
|||
|
|||
@Description("Can assign alarm to yourself") |
|||
@Test |
|||
public void assignAlarmToYourself() { |
|||
alarmWidget.assignAlarmTo(alarmType, Const.TENANT_EMAIL); |
|||
|
|||
assertIsDisplayed(alarmWidget.assignedUser(Const.TENANT_EMAIL)); |
|||
} |
|||
|
|||
@Description("Can reassign alarm to another user") |
|||
@Test |
|||
public void reassignAlarm() { |
|||
alarmWidget.assignAlarmTo(assignedAlarmType, userWithNameEmail); |
|||
|
|||
assertIsDisplayed(alarmWidget.assignedUser(userName)); |
|||
} |
|||
|
|||
@Description("Can unassign alarm") |
|||
@Test |
|||
public void unassignedAlarm() { |
|||
alarmWidget.unassignedAlarm(assignedAlarmType); |
|||
|
|||
assertIsDisplayed(alarmWidget.unassigned(assignedAlarmType)); |
|||
} |
|||
|
|||
@Description("Assign alarm to yourself from details of alarm") |
|||
@Test |
|||
public void assignAlarmToYourselfFromDetails() { |
|||
alarmWidget.alarmDetailsBtn(alarmType).click(); |
|||
alarmDetailsView.assignAlarmTo(Const.TENANT_EMAIL); |
|||
alarmDetailsView.closeAlarmDetailsViewBtn().click(); |
|||
|
|||
assertIsDisplayed(alarmWidget.assignedUser(Const.TENANT_EMAIL)); |
|||
} |
|||
|
|||
@Description("Reassign alarm to another user from details of alarm") |
|||
@Test |
|||
public void reassignAlarmFromDetails() { |
|||
alarmWidget.alarmDetailsBtn(assignedAlarmType).click(); |
|||
alarmDetailsView.assignAlarmTo(userWithNameEmail); |
|||
alarmDetailsView.closeAlarmDetailsViewBtn().click(); |
|||
|
|||
assertIsDisplayed(alarmWidget.assignedUser(userName)); |
|||
} |
|||
|
|||
@Description("Unassign alarm from details of alarm") |
|||
@Test |
|||
public void unassignedAlarmFromDetails() { |
|||
alarmWidget.alarmDetailsBtn(assignedAlarmType).click(); |
|||
alarmDetailsView.unassignedAlarm(); |
|||
alarmDetailsView.closeAlarmDetailsViewBtn().click(); |
|||
|
|||
assertIsDisplayed(alarmWidget.unassigned(assignedAlarmType)); |
|||
} |
|||
|
|||
@Description("Search by email") |
|||
@Test |
|||
public void searchByEmail() { |
|||
alarmWidget.searchAlarm(alarmType, Const.TENANT_EMAIL); |
|||
alarmWidget.setUsers(); |
|||
|
|||
assertThat(alarmWidget.getUsers()).hasSize(1).as("Search result contains search input").contains(Const.TENANT_EMAIL); |
|||
alarmWidget.assignUsers().forEach(this::assertIsDisplayed); |
|||
} |
|||
|
|||
@Description("Search by name") |
|||
@Test(groups = "broken") |
|||
public void searchByName() { |
|||
alarmWidget.searchAlarm(alarmType, userName); |
|||
alarmWidget.setUsers(); |
|||
|
|||
assertThat(alarmWidget.getUsers()).hasSize(1).as("Search result contains search input").contains(userName); |
|||
alarmWidget.assignUsers().forEach(this::assertIsDisplayed); |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,51 @@ |
|||
diff --git a/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs b/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs
|
|||
index e3457ea..a069460 100644
|
|||
--- a/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs
|
|||
+++ b/node_modules/@mat-datetimepicker/core/esm2020/datetimepicker/clock.mjs
|
|||
@@ -259,9 +259,9 @@ export class MatDatetimepickerClockComponent {
|
|||
value = 0; |
|||
} |
|||
// Don't close the minutes view if an invalid minute is clicked. |
|||
- if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|||
- return;
|
|||
- }
|
|||
+ // if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|||
+ // return;
|
|||
+ // }
|
|||
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
|||
} |
|||
this._timeChanged = true; |
|||
diff --git a/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs b/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs
|
|||
index 7699ff6..01aad13 100644
|
|||
--- a/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs
|
|||
+++ b/node_modules/@mat-datetimepicker/core/fesm2015/mat-datetimepicker-core.mjs
|
|||
@@ -951,9 +951,9 @@ class MatDatetimepickerClockComponent {
|
|||
value = 0; |
|||
} |
|||
// Don't close the minutes view if an invalid minute is clicked. |
|||
- if (!((_b = this._minutes.find((m) => (m === null || m === void 0 ? void 0 : m['value']) === value)) === null || _b === void 0 ? void 0 : _b['enabled'])) {
|
|||
- return;
|
|||
- }
|
|||
+ // if (!((_b = this._minutes.find((m) => (m === null || m === void 0 ? void 0 : m['value']) === value)) === null || _b === void 0 ? void 0 : _b['enabled'])) {
|
|||
+ // return;
|
|||
+ // }
|
|||
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
|||
} |
|||
this._timeChanged = true; |
|||
diff --git a/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs b/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs
|
|||
index 809a57d..f712b84 100644
|
|||
--- a/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs
|
|||
+++ b/node_modules/@mat-datetimepicker/core/fesm2020/mat-datetimepicker-core.mjs
|
|||
@@ -946,9 +946,9 @@ class MatDatetimepickerClockComponent {
|
|||
value = 0; |
|||
} |
|||
// Don't close the minutes view if an invalid minute is clicked. |
|||
- if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|||
- return;
|
|||
- }
|
|||
+ // if (!this._minutes.find((m) => m?.['value'] === value)?.['enabled']) {
|
|||
+ // return;
|
|||
+ // }
|
|||
date = this._adapter.createDatetime(this._adapter.getYear(this.activeDate), this._adapter.getMonth(this.activeDate), this._adapter.getDate(this.activeDate), this._adapter.getHour(this.activeDate), value); |
|||
} |
|||
this._timeChanged = true; |
|||
@ -0,0 +1,108 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// 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.
|
|||
///
|
|||
|
|||
import { |
|||
coerceArray as coerceArrayAngular, |
|||
coerceBooleanProperty, |
|||
coerceCssPixelValue as coerceCssPixelValueAngular, |
|||
coerceNumberProperty, |
|||
coerceStringArray as coerceStringArrayAngular |
|||
} from '@angular/cdk/coercion'; |
|||
|
|||
export const coerceBoolean = () => (target: any, key: string): void => { |
|||
const getter = function() { |
|||
return this['__' + key]; |
|||
}; |
|||
|
|||
const setter = function(next: any) { |
|||
this['__' + key] = coerceBooleanProperty(next); |
|||
}; |
|||
|
|||
Object.defineProperty(target, key, { |
|||
get: getter, |
|||
set: setter, |
|||
enumerable: true, |
|||
configurable: true, |
|||
}); |
|||
}; |
|||
|
|||
export const coerceNumber = () => (target: any, key: string): void => { |
|||
const getter = function(): number { |
|||
return this['__' + key]; |
|||
}; |
|||
|
|||
const setter = function(next: any) { |
|||
this['__' + key] = coerceNumberProperty(next); |
|||
}; |
|||
|
|||
Object.defineProperty(target, key, { |
|||
get: getter, |
|||
set: setter, |
|||
enumerable: true, |
|||
configurable: true, |
|||
}); |
|||
}; |
|||
|
|||
export const coerceCssPixelValue = () => (target: any, key: string): void => { |
|||
const getter = function(): string { |
|||
return this['__' + key]; |
|||
}; |
|||
|
|||
const setter = function(next: any) { |
|||
this['__' + key] = coerceCssPixelValueAngular(next); |
|||
}; |
|||
|
|||
Object.defineProperty(target, key, { |
|||
get: getter, |
|||
set: setter, |
|||
enumerable: true, |
|||
configurable: true, |
|||
}); |
|||
}; |
|||
|
|||
export const coerceArray = () => (target: any, key: string): void => { |
|||
const getter = function(): any[] { |
|||
return this['__' + key]; |
|||
}; |
|||
|
|||
const setter = function(next: any) { |
|||
this['__' + key] = coerceArrayAngular(next); |
|||
}; |
|||
|
|||
Object.defineProperty(target, key, { |
|||
get: getter, |
|||
set: setter, |
|||
enumerable: true, |
|||
configurable: true, |
|||
}); |
|||
}; |
|||
|
|||
export const coerceStringArray = (separator?: string | RegExp) => (target: any, key: string): void => { |
|||
const getter = function(): string[] { |
|||
return this['__' + key]; |
|||
}; |
|||
|
|||
const setter = function(next: any) { |
|||
this['__' + key] = coerceStringArrayAngular(next, separator); |
|||
}; |
|||
|
|||
Object.defineProperty(target, key, { |
|||
get: getter, |
|||
set: setter, |
|||
enumerable: true, |
|||
configurable: true, |
|||
}); |
|||
}; |
|||
Loading…
Reference in new issue