Funkcja Project Integrity Check
Zapewne kierując się przesłankami wynikającymi z logiki biznesowej dotyczącej przetwarzania danych takich, jak pakiet, element, relacja, atrybut czy metoda, zdecydowano o wdrożeniu wewnątrz programu EA funkcjonalności pozwalającej z poziomu interfejsu użytkownika sprawdzić spójność projektu. Funkcjonalność ta nazywa się Project Integrity Check i jest dostępna poprzez wybór w menu aplikacji opcji Tools -> Data Management -> Project Integrity Check.Po wywołaniu tej opcji wyświetlane jest następujące okno:
Domyślnie zaznaczona jest akcja Report only, dzięki której można zweryfikować poprawność danych bez wprowadzania żadnych zmian. Jeśli zdecydujemy się na automatyczne naprawienie napotkanych problemów należy wybrać Recover/Clean.
W praktyce najpierw należy wykonać pierwszy przebieg w trybie Report only, a w przypadku odnotowania nieprawidłowości wykonać drugi przebieg naprawczy Report/Clean.
Zakres weryfikacji można modyfikować poprzez zaznaczanie i odznaczanie opcji w sekcji Checks to Run. Jednakże nie widzę powodów, dla których warto byłoby modyfikować domyślne ustawienia.
Przykładowy wynik przedstawia poniższy rysunek.
Przed wykonaniem operacji naprawiającej integralność wyświetlane jest stosowne ostrzeżenie.
W rezultacie akcji wyczyszczenia repozytorium mogą być dane, które zostały uznane za niespójne mogą być usunięte lub odzyskane.
Odzyskane dane są widoczne w oknie Project Browser w specjalnym drzewie o nazwie _recovered_.
Są one podzielone na widoki zawierające osobno diagramy, elementy i pakiety.
Jeśli uznamy, że określone dane należy odzyskać, wówczas można je przenieść do właściwych pakietów.
Jednak jak dotąd nigdy wśród odzyskanych danych nie znalazłem takich, które rzeczywiście zaginęły i wymagałyby odzyskania.
Czy stosować Project Integrity Check?
Twierdzenie, że nie byłem w sytuacji, gdy odzyskałem dane przy użyciu tej funkcjonalności nie znaczy wcale, że nie nie należy jej stosować.Jeśli repozytorium posiada dane, które nie są spójne program nas nie informuje żadnymi komunikatami o błędach. Po prostu prezentuje nam dane w najlepszy sposób, w jaki potrafi. Zapewne posiada w kodzie mechanizmy, które pozwalają wyświetlić, a nawet modyfikować takie dane. Domyślam się tylko, że niespójność repozytorium może wpływać negatywnie na wydajność, co może być odczuwalne przy bardziej złożonych operacjach, takich jak na przykład Model Compare.
Kiedy należy stosować Project Integrity Check?
Przede wszystkim w następujących sytuacjach:- Z modelu korzysta wielu użytkowników, których połączenia sieciowe mogą być przerywane. Wystarczy, że jest prawdopodobieństwo, że ktoś bez zamykania projektu wypnie z sieci laptop lub zostanie zerwane połączenie VPN.
- Model jest skonfigurowany z systemem kontroli wersji. W takim przypadku mamy do czynienia z wieloma operacji importu plików XMI. Zawartość takich plików może pochodzić z innego modelu a połączenie takich danych może skutkować utratą integralności na przykład w zakresie użytych stereotypów, czy relacji do elementów spoza pliku XMI.
- Model bywa aktualizowany w oparciu o pliki XMI przy użyciu funkcji Import Package from XMI lub Batch XMI Import. Sytuacja jest analogiczna do powyższej.
- Model jest zintegrowany z innymi aplikacjami/systemami, takimi jak HP Quality Center, Mantis itp.
- Model jest wykorzystywany przez jednego użytkownika w postaci lokalnego pliku EAP, jednakże podczas wykonywania jakiejś operacji program EA lub system operacyjny uległ awarii.
Ryzyko stosowania Project Integrity Check
No dobrze, ale skoro przed wykonaniem operacji naprawy spójności danych wyświetlane jest ostrzeżenie, w którym mowa o tym, że może to wnieść znaczące zmiany w modelu, to znaczy, że jest to operacja ryzykowna. Administrator modelu, który naciśnie przycisk OK bierze na siebie odpowiedzialność za zmiany, które zostaną wprowadzone poza jego kontrolą. A co ważniejsze, program nie informuje o tym, których obiektów modelu te zmiany dotyczą! Otrzymujemy tylko informację typu:- Invalid connector, a rekomendowaną akcją (tą, która zostanie wykonana) jest - Delete Connector,
- Orphaned object wraz z akcją: Delete Package Object,
- 91 Orphaned element stereotypes wraz z akcją: Delete orphaned stereotypes.
Ale po pierwsze, jak już wspominałem nie zdarzyło mi się ani odzyskiwać ani utracić żadnych potrzebnych danych. Najczęściej usuwane były tylko jakieś nieaktualne i zagubione obiekty, które zaśmiecały repozytorium i były niewidoczne z poziomu Project Browser lub diagramu.
Po drugie, aby zapobiec niespodziewanemu użyciu tej opcji można włączyć moduł Security i ograniczyć uprawnienia do Project Integrity Check tylko dla określonej grupy użytkowników.
Po trzecie, należy wcześniej wykonać kopię zapasową.
Lista sprawdzeń Project Integrity Check
Jeśli ktoś chce mieć jednak kontrolę nad automatycznymi mechanizmami wykorzystywanymi przez program zamieszczam poniżej zestawienie zapytań SQL, które są wykonywane do sprawdzenia integralności projektu. Zapytania te są zgodne z MySQL, w przypadku innych silników bazodanowych może być konieczna ich modyfikacja, aby były zgodne z wykorzystywanym dialektem SQL.W przypadku, gdy EA znajdzie jakieś nieprawidłowości, możemy samodzielnie zmienić zapytanie w taki sposób, aby na przykład zamiast zwracać liczbę (count(*)) zwracało nazwy lub identyfikatory obiektów.
Package Structure
L.p. | Zapytanie SQL | Znaczenie |
1 | SELECT Count(*) as RecCnt FROM t_package child LEFT JOIN t_package parent on parent.Package_ID = child.Parent_ID where parent.Package_ID Is NULL and child.Parent_ID <> 0 | Zwraca osierocone pakiety, które nie mają pakietu nadrzędnego ani podrzędnego |
Object Structure
L.p. | Zapytanie SQL | Znaczenie |
2 | SELECT t_diagram.Diagram_ID, t_diagram.Name, t_diagram.ParentID, t_object.Object_ID FROM t_diagram LEFT OUTER JOIN t_object ON t_diagram.Package_ID = t_object.Package_ID AND t_diagram.ParentID = t_object.Object_ID WHERE (t_object.Object_ID IS NULL) AND (t_diagram.ParentID <> 0) | Zwraca osierocone diagramy, które nie są podpięte ani pod żaden pakiet, ani pod żaden element |
3 | SELECT t_diagram.Diagram_ID, t_diagram.Name, t_diagram.ParentID, 'NULL' FROM t_diagram WHERE t_diagram.ParentID IS NULL | Inna forma wyszukania diagramów, które powinny być podpięte pod element |
4 | select t_object.Name as OName, t_object.Object_ID as ID from t_object left join t_package on t_object.PDATA1 = t_package.Package_ID where t_package.Package_ID is null and t_object.Object_Type = 'Package' | Zwraca elementy typu pakiet, dla których brak odpowiednika w tabeli t_package |
5 | Select t_diagram.Diagram_ID as DiagramID, t_diagram.Name as DiagramName, t_diagram.Diagram_Type as DiagramType from t_diagram left join t_package on t_package.Package_ID = t_diagram.Package_ID where t_package.Package_ID is null or t_package.Parent_ID = 0 | Również zwraca osierocone diagramy, które nie są podpięte ani pod żaden pakiet, ani pod żaden element |
6 | SELECT t_package.Package_ID, t_package.Name AS PackageName, t_object.Name AS ObjectName FROM t_package LEFT OUTER JOIN t_object ON (Object_Type='Package' AND t_package.Package_ID = t_object.PDATA1) WHERE t_package.Name <> t_object.Name | Zwraca pakiety, których nazwa różni się od odpowiednika pakietu w tabeli t_object |
7 | SELECT COUNT(*) AS RecCnt FROM t_package WHERE Package_ID = Parent_ID | Zwraca ilość pakietów, które są nadrzędne wobec samego siebie |
8 | Select count(*) as RecCnt from t_object left join t_package on t_package.Package_ID = t_object.Package_ID where t_package.Package_ID is null and (t_object.ParentID is null or t_object.ParentID=0) | Zwraca ilość pakietów, dla których brak odpowiednika w tabeli t_object |
9 | Select count(*) as RecCnt FROM t_object child left join t_object parent on child.ParentID = parent.Object_ID where child.ParentID is not null and child.ParentID <> 0 and parent.Object_ID is null | Zwraca ilość elementów, które są nadrzędne wobec samego siebie |
10 | Select count(*) as RecCnt FROM t_object where ParentID = Object_ID | Inna forma powyższego zapytania |
11 | Select count(*) as RecCnt FROM t_object child left join t_object parent on child.ParentID = parent.Object_ID and parent.ParentID = child.Object_ID where parent.Object_ID is not null and child.Object_ID <> child.ParentID | Inna forma powyższego zapytania |
12 | Select count(*) as RecCnt FROM t_object child left join t_object parent on child.ParentID = parent.Object_ID where parent.Object_Type = 'Package' | Inna forma wyszukania pakietów, które są nadrzędne wobec samego siebie |
Objects Features
L.p. | Zapytanie SQL | Znaczenie |
13 | Select Object_ID, Name FROM t_object where Object_Type is null or Object_Type = '' or ( Name='EA_IMPORT_STUB' and Package_ID=1 and Object_Type='Class') | Zwraca elementy, które nie mają określonego typu |
14 | select f.Object_ID, f.FileName from t_objectfiles f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘File’, które nie są przypisane do żadnego elementu |
15 | select f.Object_ID, f.Effort from t_objecteffort f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Effort’, które nie są przypisane do żadnego elementu |
16 | select f.Object_ID, f.Metric from t_objectmetrics f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Metric’, które nie są przypisane do żadnego elementu |
17 | select f.Object_ID, f.Problem from t_objectproblems f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Problem’, które nie są przypisane do żadnego elementu |
18 | select f.Object_ID, f.Requirement from t_objectrequires f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu [internal] ‘Requirement’, które nie są przypisane do żadnego elementu |
19 | select f.Object_ID, f.`Resource` from t_objectresource f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Resource’, które nie są przypisane do żadnego elementu |
20 | select f.Object_ID, f.Risk from t_objectrisks f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Risk’, które nie są przypisane do żadnego elementu |
21 | select f.Object_ID, f.Scenario from t_objectscenarios f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Scenario’, które nie są przypisane do żadnego elementu |
22 | select f.Object_ID, f.Test from t_objecttests f left join t_object o on f.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca atrybuty elementu typu ‘Test’, które nie są przypisane do żadnego elementu |
23 | select Object_ID, ConstraintType from t_objectconstraint where ConstraintType is null or ConstraintType='' | Zwraca atrybuty elementu typu ‘Constraint’, które nie mają określonego typu |
24 | select Object_ID, `Constraint` from t_objectconstraint where `Constraint` is null or `Constraint`='' | Zwraca atrybuty elementu typu ‘Constraint’, które nie mają określonej nazwy |
25 | select Instance_ID, 'Missing Object' as Problem from t_diagramobjects l left join t_object c on l.Object_ID = c.Object_ID where c.Object_ID is null UNION select Instance_ID, 'Missing Diagram' as Problem from t_diagramobjects l left join t_diagram d on l.Diagram_ID = d.Diagram_ID where d.Diagram_ID is null | Zwraca
brakujące obiekty, które powinny być wyświetlane na diagramach oraz
brakujące diagramy, dla których istnieją obiekty do wyświetlenia |
26 | Select child.Object_ID as ID, child.Name as ObjectName, child.Object_Type, parent.Package_ID as PkgID FROM t_object child, t_object parent where child.ParentID = parent.Object_ID and child.Package_ID <> parent.Package_ID | Zwraca osierocone elementy, które mają wskazany nieprawidłowo element nadrzędny |
27 | select op.* from t_operation op left join t_object obj on op.Object_ID = obj.Object_ID where obj.Object_ID is null | Zwraca operacje, które nie są przypisane do żadnego elementu |
28 | select att.* from t_attribute att left join t_object obj on att.Object_ID = obj.Object_ID where obj.Object_ID is null | Zwraca atrybuty, które nie są przypisane do żadnego elementu |
29 | select p.* from t_objectproperties p left join t_object o on p.Object_ID = o.Object_ID where o.Object_ID is null | Zwraca tagged values, które nie są przypisane do żadnego elementu |
All GUIDs
L.p. | Zapytanie SQL | Znaczenie |
30 | Select * from t_object where ea_guid is null | Zwraca elementy, które nie posiadają GUID |
31 | Select * from t_attribute where ea_guid is null | Zwraca atrybuty, które nie posiadają GUID |
32 | Select * from t_operation where ea_guid is null | Zwraca operacje, które nie posiadają GUID |
33 | Select * from t_attributetag where ea_guid is null | Zwraca opisy atrybutów, które nie posiadają GUID |
34 | Select * from t_operationtag where ea_guid is null | Zwraca opisy operacji, które nie posiadają GUID |
35 | Select * from t_objectproperties where ea_guid is null | Zwraca atrybuty tagged values, które nie posiadają GUID |
36 | Select * from t_diagram where ea_guid is null | Zwraca diagramy, które nie posiadają GUID |
37 | Select * from t_operationparams where ea_guid is null | Zwraca parametry operacji, które nie posiadają GUID |
38 | Select * from t_package where ea_guid is null | Zwraca pakiety, które nie posiadają GUID |
39 | Select * from t_stereotypes where ea_guid is null | Zwraca nazwy stereotypów, które nie posiadają GUID |
40 | Select * from t_connector where ea_guid is null | Zwraca konektory, które nie posiadają GUID |
41 | Select * from t_connectortag where ea_guid is null | Zwraca opisy konektorów, które nie posiadają GUID |
42 | Select * from t_taggedvalue where PropertyID is null | Zwraca typy tagged values, które nie posiadają GUID |
43 | select space(40) as ea_guid, space(18) as FieldAlias, space(18) as TableName, space(11) as FieldName, 0 as Rank UNION ALL select ea_guid, 'Package GUID' as FieldAlias, 't_package' as TableName, 'ea_guid' as FieldName, 1 as Rank from t_package UNION ALL select ea_guid, 'Object GUID' as FieldAlias, 't_object' as TableName, 'ea_guid' as FieldName, 2 as Rank from t_object where Object_Type <> 'Package' UNION ALL select ea_guid, 'Diagram GUID' as FieldAlias, 't_diagram' as TableName, 'ea_guid' as FieldName, 3 as Rank from t_diagram UNION ALL select ea_guid, 'Stereotype GUID' as FieldAlias, 't_stereotypes' as TableName, 'ea_guid' as FieldName, 4 as Rank from t_stereotypes UNION ALL select ea_guid, 'Attribute GUID' as FieldAlias, 't_attribute' as TableName, 'ea_guid' as FieldName, 5 as Rank from t_attribute UNION ALL select ea_guid, 'Operation GUID' as FieldAlias, 't_operation' as TableName, 'ea_guid' as FieldName, 6 as Rank from t_operation UNION ALL select ea_guid, 'Parameter GUID' as FieldAlias, 't_operationparams' as TableName, 'ea_guid' as FieldName, 7 as Rank from t_operationparams UNION ALL select ea_guid, 'Connector GUID' as FieldAlias, 't_connector' as TableName, 'ea_guid' as FieldName, 8 as Rank from t_connector UNION ALL select ea_guid, 'Object Tag GUID' as FieldAlias, 't_objectproperties' as TableName, 'ea_guid' as FieldName, 9 as Rank from t_objectproperties UNION ALL select ea_guid, 'Connector Tag GUID' as FieldAlias, 't_connectortag' as TableName, 'ea_guid' as FieldName, 10 as Rank from t_connectortag UNION ALL select ea_guid, 'Attribute Tag GUID' as FieldAlias, 't_attributetag' as TableName, 'ea_guid' as FieldName, 11 as Rank from t_attributetag UNION ALL select ea_guid, 'Operation Tag GUID' as FieldAlias, 't_operationtag' as TableName, 'ea_guid' as FieldName, 12 as Rank from t_operationtag UNION ALL select PropertyID as ea_guid, 'Generic Tag GUID' as FieldAlias, 't_taggedvalue' as TableName, 'PropertyID' as FieldName, 13 as Rank from t_taggedvalue UNION ALL select ea_guid, 'Scenario GUID' as FieldAlias, 't_objectscenarios' as TableName, 'ea_guid' as FieldName, 14 as Rank from t_objectscenarios ORDER BY 1, 5 | Może ktoś podpowie do czego może służyć to zapytanie |
Verify Cross References
L.p. | Zapytanie SQL | Znaczenie |
44 | Select Client, `Type`, Name, Behavior from t_xref where Name in ('Stereotypes', 'CustomProperties') Group by Client, `Type`, Name, Behavior having count(XRefID) >1 | Zapewne to weryfikacja stereotypów. Ale może ktoś podpowie czego dotyczy ta weryfikacja |
45 | select XrefID from t_xref left outer join t_object on t_xref.Client = t_object.ea_guid where t_xref.`Type`='element property' and t_xref.Name='Stereotypes' and t_object.ea_guid is null UNION select XrefID from t_xref left outer join t_attribute on t_xref.Client = t_attribute.ea_guid where t_xref.`Type`='attribute property' and t_xref.Name='Stereotypes' and t_attribute.ea_guid is null UNION select XrefID from t_xref left outer join t_operation on t_xref.Client = t_operation.ea_guid where t_xref.`Type`='operation property' and t_xref.Name='Stereotypes' and t_operation.ea_guid is null UNION select XrefID from t_xref left outer join t_operationparams on t_xref.Client = t_operationparams.ea_guid where t_xref.`Type`='parameter property' and t_xref.Name='Stereotypes' and t_operationparams.ea_guid is null UNION select XrefID from t_xref left outer join t_connector on t_xref.Client = t_connector.ea_guid where t_xref.`Type`='connector property' and t_xref.Name='Stereotypes' and t_connector.ea_guid is null UNION select XrefID from t_xref left outer join t_connector on t_xref.Client = t_connector.ea_guid where t_xref.`Type`='connectorSrcEnd property' and t_xref.Name='Stereotypes' and t_connector.ea_guid is null UNION select XrefID from t_xref left outer join t_connector on t_xref.Client = t_connector.ea_guid where t_xref.`Type`='connectorDestEnd property' and t_xref.Name='Stereotypes' and t_connector.ea_guid is null | Zwraca listę osieroconoych stereotypów, które nie są wykorzystywane przez elementy, atrybuty, metody, parametry metod lub relacje |
Connectors
L.p. | Zapytanie SQL | Znaczenie |
46 | select count(*) as numrows from t_connector where Connector_Type is null | Zwraca liczbę relacji, dla których brakuje typu |
47 | Select t_connector.Connector_ID, t_connector.Connector_Type from t_connector left join t_object on t_connector.End_Object_ID = t_object.Object_ID where t_object.Object_ID is null or ( t_object.Name='EA_IMPORT_STUB' and t_object.Package_ID=1 and t_object.Object_Type='Class') UNION Select t_connector.Connector_ID, t_connector.Connector_Type from t_connector left join t_object on t_connector.Start_Object_ID = t_object.Object_ID where t_object.Object_ID is null or ( t_object.Name='EA_IMPORT_STUB' and t_object.Package_ID=1 and t_object.Object_Type='Class') | Zwraca listę relacji, dla których brakuje albo elementu źródłowego albo docelowego |
48 | select Instance_ID, 'Missing Connector' as Problem from t_diagramlinks l left join t_connector c on l.ConnectorID = c.Connector_ID where c.Connector_ID is null UNION select Instance_ID, 'Missing Diagram' as Problem from t_diagramlinks l left join t_diagram d on l.DiagramID = d.Diagram_ID where d.Diagram_ID is null | Zwraca listę relacji, które powinny być wyświetlane na diagramach, ale brakuje dla nich relacji w modelu |
49 | select count(*) as RecCount, DiagramID, ConnectorID,'Duplicate Link Information' as Problem from t_diagramlinks group by DiagramID, ConnectorID having count(*) > 1 | Zwraca listę relacji, które są wyświetlane na diagramach, dla których istnieje więcej niż jedna relacja w modelu |
50 | select * from t_connectorconstraint c left join t_connector o on c.ConnectorID = o.Connector_ID where o.Connector_ID is null | Zwraca listę ograniczeń przypisanych do relacji, dla których brakuje relacji |
51 | select * from t_connectortag c left join t_connector o on c.ElementID = o.Connector_ID where o.Connector_ID is null | Zwraca listę tagów przypisanych do relacji, dla których brakuje relacji |
52 | SELECT t_connector.Connector_ID, t_connector.Connector_Type, t_diagram.Diagram_ID, t_connector.DiagramID FROM t_connector LEFT JOIN t_diagram ON t_connector.DiagramID = t_diagram.Diagram_ID WHERE (((t_diagram.Diagram_ID) Is Null) AND ((t_connector.DiagramID)<>0)) | Sprawdza spójność pomiędzy relacjami w modelu a relacjami prezentowanymi na diagramach |
53 | SELECT t_connector.Connector_ID, t_connector.SourceRole, t_connector.DestRole, t_connector.StyleEx FROM t_connector, t_operation WHERE t_connector.Start_Object_ID = t_operation.Object_ID AND t_operation.Stereotype = 'FK' GROUP BY Connector_ID | Weryfikuje poprawność relacji typu foreign key |
54 | select Connector_ID from t_connector where Connector_Type='Realization' | Sprawdza poprawność, bo powinno być ‘Realisation’ |
55 | select Stereotype from t_stereotypes where AppliesTo='realization' | Sprawdza, czy istnieją stereotypy, które są stosowane do relacji ‘realization’ zamiast ‘realisation’ |
56 | select c.* from t_attributeconstraints c left join t_attribute o on c.ID = o.ID where o.ID is null | Zwraca listę constraints atrybutów, dla których nie znaleziono atrybutu |
57 | select * from t_attributetag c left join t_attribute o on c.ElementID = o.ID where o.ID is null | Zwraca listę tagów atrybutów, dla których nie znaleziono atrybutu |
58 | select * from t_attribute where t_attribute.Name is null | Zwraca listę atrybutów bez nazwy |
59 | select o.*, c.OperationID as OpID, c.PostCondition from t_operationposts c left join t_operation o on c.OperationID = o.OperationID where o.OperationID is null | Zwraca listę warunków końcowych dla metod, dla których nie znaleziono metody |
60 | select o.*, c.OperationID as OpID, c.PreCondition from t_operationpres c left join t_operation o on c.OperationID = o.OperationID where o.OperationID is null | Zwraca listę warunków początkowych dla metod, dla których nie znaleziono metody |
61 | select * from t_operationtag c left join t_operation o on c.ElementID = o.OperationID where o.OperationID is null | Zwraca listę tagów metody, dla których nie znaleziono metody |
62 | select c.* from t_operationparams c left join t_operation o on c.OperationID = o.OperationID where o.OperationID is null | Zwraca listę parametrów metody, dla których nie znaleziono metody |
63 | select 0 as tabletype, `Type`, Classifier, ea_guid from t_attribute where Classifier is not null and Classifier <> '0' union select 1 as tabletype, `Type`, Classifier, ea_guid from t_operation where Classifier is not null and Classifier <> '0' union select 2 as tabletype, `Type`, Classifier, ea_guid from t_operationparams where Classifier is not null and Classifier <> '0' | Może ktoś podpowie do czego to może służyć |
Thanks for the queries!
OdpowiedzUsuń