From 5ca42da5cef9f9a1202f5ccfbb30fca76807edb2 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 22 Mar 2021 15:13:26 +0800 Subject: [PATCH 1/5] =?UTF-8?q?refactor(=E6=80=A7=E8=83=BD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95):=20k8s=20job=E8=AE=BE=E7=BD=AE=E5=8F=8D=E4=BA=B2?= =?UTF-8?q?=E5=92=8C=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/io/metersphere/xpack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index 2fb883803f..e20541d8c8 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit 2fb883803fa9909162ce9f6fab8a15b63af66923 +Subproject commit e20541d8c864216ebdaade01226b795efeb10c48 From bb4f30a9a139ccb99b877f9afc9b1fcce467deda Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Mon, 22 Mar 2021 15:32:00 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix(=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95):?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E8=AE=A1=E7=AE=97=E6=80=BB=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E6=95=B0=E6=B2=A1=E6=9C=89=E6=8E=92=E9=99=A4=E7=A6=81?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E7=BA=BF=E7=A8=8B=E7=BB=84=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../performance/engine/AbstractEngine.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java b/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java index 5c5e9d88fb..f67454fa1a 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java +++ b/backend/src/main/java/io/metersphere/performance/engine/AbstractEngine.java @@ -19,8 +19,8 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.util.List; -import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; public abstract class AbstractEngine implements Engine { protected String JMETER_IMAGE; @@ -106,17 +106,20 @@ public abstract class AbstractEngine implements Engine { String loadConfiguration = t.getLoadConfiguration(); JSONArray jsonArray = JSON.parseArray(loadConfiguration); for (int i = 0; i < jsonArray.size(); i++) { - if (jsonArray.get(i) instanceof Map) { - JSONObject o = jsonArray.getJSONObject(i); - if (StringUtils.equals(o.getString("key"), "TargetLevel")) { - s = o.getInteger("value"); - break; - } - } if (jsonArray.get(i) instanceof List) { JSONArray o = jsonArray.getJSONArray(i); - for (int j = 0; j < o.size(); j++) { - JSONObject b = o.getJSONObject(j); + List enabledConfig = o.stream() + .filter(b -> { + JSONObject c = JSON.parseObject(b.toString()); + if (StringUtils.equals(c.getString("deleted"), "true")) { + return false; + } + return !StringUtils.equals(c.getString("enabled"), "false"); + }) + .map(b -> JSON.parseObject(b.toString())) + .collect(Collectors.toList()); + + for (JSONObject b : enabledConfig) { if (StringUtils.equals(b.getString("key"), "TargetLevel")) { s += b.getInteger("value"); break; From fa0a57a6af7b2964148006a2f60da15357e36460 Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Mon, 22 Mar 2021 15:44:00 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E6=89=A7=E8=A1=8C=E7=94=A8=E4=BE=8B=E6=97=B6=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=85=B3=E8=81=94=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/ext/ExtTestPlanTestCaseMapper.java | 3 ++ .../mapper/ext/ExtTestPlanTestCaseMapper.xml | 3 ++ .../track/dto/TestCaseTestDTO.java | 11 ++++ .../track/dto/TestPlanCaseDTO.java | 4 ++ .../service/TestPlanTestCaseService.java | 51 ++++++++++++++++--- .../src/business/components/api/router.js | 4 +- .../functional/FunctionalTestCaseEdit.vue | 34 ++++++++++++- 7 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/track/dto/TestCaseTestDTO.java diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java index d196782bf1..d144342a68 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.java @@ -1,6 +1,7 @@ package io.metersphere.base.mapper.ext; import io.metersphere.track.dto.TestCaseReportStatusResultDTO; +import io.metersphere.track.dto.TestCaseTestDTO; import io.metersphere.track.dto.TestPlanCaseDTO; import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest; import org.apache.ibatis.annotations.Param; @@ -47,4 +48,6 @@ public interface ExtTestPlanTestCaseMapper { List getExecResultByPlanId(String planId); List listForMinder(@Param("planId") String planId); + + List listTestCaseTest(@Param("caseId") String caseId); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml index 80c68f3fbc..495d5ed733 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanTestCaseMapper.xml @@ -421,6 +421,9 @@ pc.plan_id = #{planId} + update test_plan_test_case diff --git a/backend/src/main/java/io/metersphere/track/dto/TestCaseTestDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestCaseTestDTO.java new file mode 100644 index 0000000000..042bc04368 --- /dev/null +++ b/backend/src/main/java/io/metersphere/track/dto/TestCaseTestDTO.java @@ -0,0 +1,11 @@ +package io.metersphere.track.dto; + +import io.metersphere.base.domain.TestCaseTest; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TestCaseTestDTO extends TestCaseTest { + private String testName; +} diff --git a/backend/src/main/java/io/metersphere/track/dto/TestPlanCaseDTO.java b/backend/src/main/java/io/metersphere/track/dto/TestPlanCaseDTO.java index d677de4b0b..3540c43f19 100644 --- a/backend/src/main/java/io/metersphere/track/dto/TestPlanCaseDTO.java +++ b/backend/src/main/java/io/metersphere/track/dto/TestPlanCaseDTO.java @@ -3,6 +3,8 @@ package io.metersphere.track.dto; import io.metersphere.base.domain.TestCaseWithBLOBs; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Getter @Setter @@ -18,4 +20,6 @@ public class TestPlanCaseDTO extends TestCaseWithBLOBs { private String reportId; private String model; private String projectName; + + private List list; } diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java index 3b103d1aaf..221fdae702 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanTestCaseService.java @@ -1,11 +1,8 @@ package io.metersphere.track.service; import com.github.pagehelper.PageHelper; -import io.metersphere.base.domain.TestPlan; -import io.metersphere.base.domain.TestPlanTestCaseExample; -import io.metersphere.base.domain.TestPlanTestCaseWithBLOBs; -import io.metersphere.base.domain.User; -import io.metersphere.base.mapper.TestPlanTestCaseMapper; +import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper; import io.metersphere.commons.constants.TestPlanTestCaseStatus; import io.metersphere.commons.user.SessionUser; @@ -14,6 +11,7 @@ import io.metersphere.commons.utils.ServiceUtils; import io.metersphere.commons.utils.SessionUtils; import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.service.UserService; +import io.metersphere.track.dto.TestCaseTestDTO; import io.metersphere.track.dto.TestPlanCaseDTO; import io.metersphere.track.request.testcase.TestPlanCaseBatchRequest; import io.metersphere.track.request.testplancase.QueryTestPlanCaseRequest; @@ -42,6 +40,14 @@ public class TestPlanTestCaseService { @Resource ExtTestPlanTestCaseMapper extTestPlanTestCaseMapper; + @Resource + private TestCaseTestMapper testCaseTestMapper; + @Resource + private LoadTestMapper loadTestMapper; + @Resource + private ApiTestCaseMapper apiTestCaseMapper; + @Resource + private ApiScenarioMapper apiScenarioMapper; public List list(QueryTestPlanCaseRequest request) { request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders())); @@ -134,7 +140,40 @@ public class TestPlanTestCaseService { } public TestPlanCaseDTO get(String testplanTestCaseId) { - return extTestPlanTestCaseMapper.get(testplanTestCaseId); + TestPlanCaseDTO testPlanCaseDTO = extTestPlanTestCaseMapper.get(testplanTestCaseId); + List testCaseTestDTOS = extTestPlanTestCaseMapper.listTestCaseTest(testPlanCaseDTO.getCaseId()); + testCaseTestDTOS.forEach(dto -> { + setTestName(dto); + }); + testPlanCaseDTO.setList(testCaseTestDTOS); + return testPlanCaseDTO; + } + + private void setTestName(TestCaseTestDTO dto) { + String type = dto.getTestType(); + String id = dto.getTestId(); + switch (type) { + case "performance": + LoadTest loadTest = loadTestMapper.selectByPrimaryKey(id); + if (loadTest != null) { + dto.setTestName(loadTest.getName()); + } + break; + case "testcase": + ApiTestCaseWithBLOBs apiTestCaseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(id); + if (apiTestCaseWithBLOBs != null) { + dto.setTestName(apiTestCaseWithBLOBs.getName()); + } + break; + case "automation": + ApiScenarioWithBLOBs apiScenarioWithBLOBs = apiScenarioMapper.selectByPrimaryKey(id); + if (apiScenarioWithBLOBs != null) { + dto.setTestName(apiScenarioWithBLOBs.getName()); + } + break; + default: + break; + } } public void deleteTestCaseBath(TestPlanCaseBatchRequest request) { diff --git a/frontend/src/business/components/api/router.js b/frontend/src/business/components/api/router.js index e34577f195..9aba2aed37 100644 --- a/frontend/src/business/components/api/router.js +++ b/frontend/src/business/components/api/router.js @@ -44,12 +44,12 @@ export default { component: () => import('@/business/components/api/report/ApiReportView'), }, { - path: "definition", + path: "definition/:redirectID?/:dataType?/:dataSelectRange?", name: "ApiDefinition", component: () => import('@/business/components/api/definition/ApiDefinition'), }, { - path: "automation", + path: "automation/:redirectID?/:dataType?/:dataSelectRange?", name: "ApiAutomation", component: () => import('@/business/components/api/automation/ApiAutomation'), }, diff --git a/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseEdit.vue b/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseEdit.vue index c4d3b1f1e5..8ee1ab81ad 100644 --- a/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseEdit.vue +++ b/frontend/src/business/components/track/plan/view/comonents/functional/FunctionalTestCaseEdit.vue @@ -85,6 +85,15 @@ + + + 关联测试: + + {{ item.testName }} + + + + {{ $t('test_track.case.prerequisite') }}: @@ -351,7 +360,7 @@ import ApiTestDetail from "../test/ApiTestDetail"; import ApiTestResult from "../test/ApiTestResult"; import PerformanceTestDetail from "../test/PerformanceTestDetail"; import PerformanceTestResult from "../test/PerformanceTestResult"; -import {listenGoBack, removeGoBackListener} from "@/common/js/utils"; +import {getUUID, listenGoBack, removeGoBackListener} from "@/common/js/utils"; import TestCaseAttachment from "@/business/components/track/case/components/TestCaseAttachment"; import CaseComment from "@/business/components/track/case/components/CaseComment"; import MsPreviousNextButton from "../../../../../common/components/MsPreviousNextButton"; @@ -559,6 +568,29 @@ export default { } }); }, + openTest(item) { + const type = item.testType; + const id = item.testId; + switch (type) { + case "performance": { + let performanceData = this.$router.resolve({ + path: '/performance/test/edit/' + id, + }) + window.open(performanceData.href, '_blank'); + break; + } + case "testcase": { + let caseData = this.$router.resolve({name:'ApiDefinition',params:{redirectID:getUUID(),dataType:"apiTestCase",dataSelectRange:'single:'+id}}); + window.open(caseData.href, '_blank'); + break; + } + case "automation": { + let automationData = this.$router.resolve({name:'ApiAutomation',params:{redirectID:getUUID(),dataType:"scenario",dataSelectRange:'edit:'+id}}); + window.open(automationData.href, '_blank'); + break; + } + } + }, getRelatedTest() { if (this.testCase.method === 'auto' && this.testCase.testId && this.testCase.testId !== 'other') { this.$get('/' + this.testCase.type + '/get/' + this.testCase.testId, response => { From 2d8423556ce7a4f993dbdb0f420777be8634e174 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Mon, 22 Mar 2021 16:18:29 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix(=E5=9C=BA=E6=99=AF=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=8C=96):=20=E4=BF=AE=E5=A4=8D=E6=B7=BB=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=AF=B7=E6=B1=82=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E5=8F=98=E9=87=8F=E6=9F=A5=E7=9C=8B=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/definition/request/MsTestElement.java | 5 + .../request/unknown/MsJmeterElement.java | 12 + .../api/service/ApiAutomationService.java | 15 +- .../automation/scenario/ApiScenarioList.vue | 3 +- .../scenario/common/ApiBaseComponent.vue | 4 +- .../scenario/variable/VariableList.vue | 8 +- .../definition/components/list/ApiList.vue | 1022 ++++++++--------- .../components/common/components/MsTag.vue | 46 +- 8 files changed, 576 insertions(+), 539 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java index 8238f27752..55fdb3b511 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/MsTestElement.java @@ -32,6 +32,7 @@ import io.metersphere.base.domain.ApiDefinitionWithBLOBs; import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs; import io.metersphere.commons.constants.LoopConstants; import io.metersphere.commons.constants.MsTestElementConstants; +import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.LogUtil; @@ -48,6 +49,7 @@ import org.apache.jorphan.collections.HashTree; import org.apache.jorphan.collections.ListedHashTree; import java.io.ByteArrayOutputStream; +import java.io.File; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -211,6 +213,9 @@ public abstract class MsTestElement { csvDataSet.setName(StringUtils.isEmpty(item.getName()) ? "CSVDataSet" : item.getName()); csvDataSet.setProperty("fileEncoding", StringUtils.isEmpty(item.getEncoding()) ? "UTF-8" : item.getEncoding()); if (CollectionUtils.isNotEmpty(item.getFiles())) { + if (new File(BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()).exists()) { + MSException.throwException(StringUtils.isEmpty(item.getName()) ? "CSVDataSet" : item.getName() + ":[ CSV文件不存在 ]"); + } csvDataSet.setProperty("filename", BODY_FILE_DIR + "/" + item.getFiles().get(0).getId() + "_" + item.getFiles().get(0).getName()); } csvDataSet.setIgnoreFirstLine(false); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/unknown/MsJmeterElement.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/unknown/MsJmeterElement.java index 38237e19f9..710ab4b672 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/unknown/MsJmeterElement.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/unknown/MsJmeterElement.java @@ -3,11 +3,14 @@ package io.metersphere.api.dto.definition.request.unknown; import com.alibaba.fastjson.annotation.JSONType; import io.metersphere.api.dto.definition.request.MsTestElement; import io.metersphere.api.dto.definition.request.ParameterConfig; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.FileUtils; import io.metersphere.commons.utils.LogUtil; import lombok.Data; import lombok.EqualsAndHashCode; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.config.CSVDataSet; import org.apache.jmeter.save.SaveService; import org.apache.jmeter.testelement.TestElement; import org.apache.jmeter.testelement.TestPlan; @@ -15,6 +18,7 @@ import org.apache.jmeter.threads.ThreadGroup; import org.apache.jorphan.collections.HashTree; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.util.List; @@ -53,6 +57,13 @@ public class MsJmeterElement extends MsTestElement { if (!config.isOperating() && scriptWrapper instanceof ThreadGroup && !((ThreadGroup) scriptWrapper).isEnabled()) { LogUtil.info(((ThreadGroup) scriptWrapper).getName() + "是被禁用线程组不加入执行"); } else { + // CSV数据检查文件路径是否还存在 + if (scriptWrapper instanceof CSVDataSet) { + String path = ((CSVDataSet) scriptWrapper).getPropertyAsString("filename"); + if (!new File(path).exists()) { + MSException.throwException(StringUtils.isEmpty(((CSVDataSet) scriptWrapper).getName()) ? "CSVDataSet" : ((CSVDataSet) scriptWrapper).getName() + ":[ CSV文件不存在 ]"); + } + } if (CollectionUtils.isNotEmpty(hashTree)) { for (MsTestElement el : hashTree) { el.toHashTree(elementTree, el.getHashTree(), config); @@ -62,6 +73,7 @@ public class MsJmeterElement extends MsTestElement { } } catch (Exception ex) { ex.printStackTrace(); + MSException.throwException(ex.getMessage()); } } diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index 8096f1e974..4b4abebdb6 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -535,8 +535,12 @@ public class ApiAutomationService { } // 生成报告和HashTree - HashTree hashTree = generateHashTree(item, reportId, planEnvMap); - + HashTree hashTree = null; + try { + hashTree = generateHashTree(item, reportId, planEnvMap); + } catch (Exception ex) { + MSException.throwException(ex.getMessage()); + } //存储报告 batchMapper.insert(report); @@ -602,7 +606,12 @@ public class ApiAutomationService { } ParameterConfig config = new ParameterConfig(); config.setConfig(envConfig); - HashTree hashTree = request.getTestElement().generateHashTree(config); + HashTree hashTree = null; + try { + hashTree = request.getTestElement().generateHashTree(config); + } catch (Exception e) { + MSException.throwException(e.getMessage()); + } // 调用执行方法 APIScenarioReportResult reportResult = createScenarioReport(request.getId(), request.getScenarioId(), request.getScenarioName(), ReportTriggerMode.MANUAL.name(), request.getExecuteType(), request.getProjectId(), SessionUtils.getUserId()); diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index b035cc76b2..141b314e7f 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -69,7 +69,7 @@ - + - + diff --git a/frontend/src/business/components/api/automation/scenario/variable/VariableList.vue b/frontend/src/business/components/api/automation/scenario/variable/VariableList.vue index 5b00d2fc71..12700397ca 100644 --- a/frontend/src/business/components/api/automation/scenario/variable/VariableList.vue +++ b/frontend/src/business/components/api/automation/scenario/variable/VariableList.vue @@ -219,8 +219,12 @@ return this.selection.includes(row.id) }, open: function (variables, headers, disabled) { - this.variables = variables; - this.headers = headers; + if(variables){ + this.variables = variables; + } + if(headers){ + this.headers = headers; + } this.visible = true; this.editData = {type: "CONSTANT"}; this.addParameters(this.editData); diff --git a/frontend/src/business/components/api/definition/components/list/ApiList.vue b/frontend/src/business/components/api/definition/components/list/ApiList.vue index 823a797568..6069e06e71 100644 --- a/frontend/src/business/components/api/definition/components/list/ApiList.vue +++ b/frontend/src/business/components/api/definition/components/list/ApiList.vue @@ -45,7 +45,7 @@ {{ scope.row.num }} {{ scope.row.num }} - + @@ -211,548 +211,548 @@