Provide default indexes for database option values instead of reordering options lists (#2156)

* Also re-add <default> to owners list for new database dialog
This commit is contained in:
Cory Rivera
2023-07-27 16:11:49 -07:00
committed by GitHub
parent 325f49ff20
commit 46fa477717
3 changed files with 100 additions and 100 deletions

View File

@@ -173,7 +173,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
((DatabaseInfo)databaseViewInfo.ObjectInfo).PageVerify = displayPageVerifyOptions[smoDatabase.PageVerify]; ((DatabaseInfo)databaseViewInfo.ObjectInfo).PageVerify = displayPageVerifyOptions[smoDatabase.PageVerify];
((DatabaseInfo)databaseViewInfo.ObjectInfo).TargetRecoveryTimeInSec = smoDatabase.TargetRecoveryTime; ((DatabaseInfo)databaseViewInfo.ObjectInfo).TargetRecoveryTimeInSec = smoDatabase.TargetRecoveryTime;
if (prototype is DatabasePrototype160) { if (prototype is DatabasePrototype160)
{
((DatabaseInfo)databaseViewInfo.ObjectInfo).IsLedgerDatabase = smoDatabase.IsLedger; ((DatabaseInfo)databaseViewInfo.ObjectInfo).IsLedgerDatabase = smoDatabase.IsLedger;
} }
} }
@@ -239,16 +240,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{ {
logins.Add(login.Name); logins.Add(login.Name);
} }
// If we don't have a default database owner, then move the current login to the front of the list to use as the default. // Add <default> to the start of the list in addition to defined logins
string firstOwner = prototype.Exists ? prototype.Owner : dataContainer.Server.ConnectionContext.TrueLogin; logins.Insert(0, SR.general_default);
int swapIndex = logins.FindIndex(login => login.Equals(firstOwner, StringComparison.InvariantCultureIgnoreCase));
if (swapIndex > 0)
{
logins.RemoveAt(swapIndex);
logins.Insert(0, firstOwner);
}
databaseViewInfo.LoginNames = logins.ToArray(); databaseViewInfo.LoginNames = new OptionsCollection() { Options = logins.ToArray(), DefaultValueIndex = 0 };
} }
return Task.FromResult(new InitializeViewResult { ViewInfo = databaseViewInfo, Context = context }); return Task.FromResult(new InitializeViewResult { ViewInfo = databaseViewInfo, Context = context });
@@ -423,7 +418,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
} }
} }
if (database.Owner != null && viewParams.IsNewObject) if (database.Owner != null && database.Owner != SR.general_default && viewParams.IsNewObject)
{ {
prototype.Owner = database.Owner; prototype.Owner = database.Owner;
} }
@@ -458,7 +453,8 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
} }
if (prototype is DatabasePrototype110 db110) if (prototype is DatabasePrototype110 db110)
{ {
if (database.TargetRecoveryTimeInSec != null) { if (database.TargetRecoveryTimeInSec != null)
{
db110.TargetRecoveryTime = (int)database.TargetRecoveryTimeInSec; db110.TargetRecoveryTime = (int)database.TargetRecoveryTimeInSec;
} }
@@ -532,56 +528,63 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
/// <summary> /// <summary>
/// Get supported database collations for this server. /// Get supported database collations for this server.
/// </summary> /// </summary>
/// <returns>A string array containing the display names of the collations. The first element will be "<default>" if this is either a new database or a Sphinx server. /// <returns>An <see cref="OptionsCollection"/> of the supported collations and the default collation's index.</returns>
private string[] GetCollations(Server server, DatabasePrototype prototype, bool isNewObject) private OptionsCollection GetCollations(Server server, DatabasePrototype prototype, bool isNewObject)
{ {
var collationItems = new List<string>(); var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
bool isSphinxServer = (server.VersionMajor < minimumVersionForWritableCollation); // Writable collations are not supported for Sphinx and earlier
if (server.VersionMajor < minimumVersionForWritableCollation)
// if the server is shiloh or later, add specific collations to the list {
if (!isSphinxServer) return options;
}
using (DataTable serverCollationsTable = server.EnumCollations())
{ {
DataTable serverCollationsTable = server.EnumCollations();
if (serverCollationsTable != null) if (serverCollationsTable != null)
{ {
var collationItems = new List<string>();
foreach (DataRow serverCollation in serverCollationsTable.Rows) foreach (DataRow serverCollation in serverCollationsTable.Rows)
{ {
string collationName = (string)serverCollation["Name"]; string collationName = (string)serverCollation["Name"];
collationItems.Add(collationName); collationItems.Add(collationName);
} }
// If this database already exists, then use its collation as the default value.
// Otherwise use the server's collation as the default value.
string firstCollation = prototype.Exists ? prototype.Collation : server.Collation;
int defaultIndex = collationItems.FindIndex(collation => collation.Equals(firstCollation, StringComparison.InvariantCultureIgnoreCase));
if (defaultIndex > 0)
{
options.DefaultValueIndex = defaultIndex;
}
options.Options = collationItems.ToArray();
} }
} }
// If this database already exists, then put its collation at the front of the list. return options;
// Otherwise use the server's collation as the default first value.
string firstCollation = prototype.Exists ? prototype.Collation : server.Collation;
int index = collationItems.FindIndex(collation => collation.Equals(firstCollation, StringComparison.InvariantCultureIgnoreCase));
if (index > 0)
{
collationItems.RemoveAt(index);
collationItems.Insert(0, firstCollation);
}
return collationItems.ToArray();
} }
/// <summary> /// <summary>
/// Gets the prototype's current collation. /// Gets the prototype's current collation.
/// </summary> /// </summary>
private string[] GetCollationsWithPrototypeCollation(DatabasePrototype prototype) /// <returns>An <see cref="OptionsCollection"/> of the prototype's collation and the default collation's index.</returns>
private OptionsCollection GetCollationsWithPrototypeCollation(DatabasePrototype prototype)
{ {
return new string[] { prototype.Collation }; return new OptionsCollection() { Options = new string[] { prototype.Collation }, DefaultValueIndex = 0 };
} }
/// <summary> /// <summary>
/// Get supported database containment types for this server. /// Get supported database containment types for this server.
/// </summary> /// </summary>
/// <returns>A string array containing the display names of the containment types. This array is empty if containment types are not supported for this server.</returns> /// <returns>An <see cref="OptionsCollection"/> of the supported containment types and the default containment type's index.</returns>
private string[] GetContainmentTypes(Server server, DatabasePrototype prototype) private OptionsCollection GetContainmentTypes(Server server, DatabasePrototype prototype)
{ {
var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
// Containment types are only supported for Denali and later, and only if the server is not a managed instance // Containment types are only supported for Denali and later, and only if the server is not a managed instance
if (!(SqlMgmtUtils.IsSql11OrLater(server.ServerVersion)) || server.IsAnyManagedInstance()) if (!(SqlMgmtUtils.IsSql11OrLater(server.ServerVersion)) || server.IsAnyManagedInstance())
{ {
return Array.Empty<string>(); return options;
} }
var containmentTypes = new List<string>(); var containmentTypes = new List<string>();
@@ -596,39 +599,36 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
containmentTypes.Add(displayContainmentTypes[ContainmentType.None]); containmentTypes.Add(displayContainmentTypes[ContainmentType.None]);
containmentTypes.Add(displayContainmentTypes[ContainmentType.Partial]); containmentTypes.Add(displayContainmentTypes[ContainmentType.Partial]);
// Put the prototype's current containment type at the front of the list // Use the prototype's current containment type as the default value
var swapIndex = 0; var defaultIndex = 0;
switch (dbContainmentType) switch (dbContainmentType)
{ {
case ContainmentType.None: case ContainmentType.None:
break; break;
case ContainmentType.Partial: case ContainmentType.Partial:
swapIndex = 1; defaultIndex = 1;
break; break;
default: default:
break; break;
} }
if (swapIndex > 0) options.DefaultValueIndex = defaultIndex;
{ options.Options = containmentTypes.ToArray();
var value = containmentTypes[swapIndex]; return options;
containmentTypes.RemoveAt(swapIndex);
containmentTypes.Insert(0, value);
}
return containmentTypes.ToArray();
} }
/// <summary> /// <summary>
/// Get supported database recovery models for this server. /// Get supported database recovery models for this server.
/// </summary> /// </summary>
/// <returns>A string array containing the display names of the recovery models. This array is empty if recovery models are not supported for this server.</returns> /// <returns>An <see cref="OptionsCollection"/> of the supported recovery models and the default recovery model's index.</returns>
private string[] GetRecoveryModels(Server server, DatabasePrototype prototype) private OptionsCollection GetRecoveryModels(Server server, DatabasePrototype prototype)
{ {
var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
// Recovery models are only supported if the server is shiloh or later and is not a Managed Instance // Recovery models are only supported if the server is shiloh or later and is not a Managed Instance
var recoveryModelEnabled = (minimumVersionForRecoveryModel <= server.VersionMajor) && !server.IsAnyManagedInstance(); var recoveryModelEnabled = (minimumVersionForRecoveryModel <= server.VersionMajor) && !server.IsAnyManagedInstance();
if (server.GetDisabledProperties().Contains("RecoveryModel") || !recoveryModelEnabled) if (server.GetDisabledProperties().Contains("RecoveryModel") || !recoveryModelEnabled)
{ {
return Array.Empty<string>(); return options;
} }
var recoveryModels = new List<string>(); var recoveryModels = new List<string>();
@@ -653,31 +653,27 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
} }
} }
// Put the prototype's current recovery model at the front of the list // Use the prototype's current recovery model as the default value
if (recoveryModelEnabled) if (recoveryModels.Count > 1)
{ {
var swapIndex = 0; var defaultIndex = 0;
switch (prototype.RecoveryModel) switch (prototype.RecoveryModel)
{ {
case RecoveryModel.BulkLogged: case RecoveryModel.BulkLogged:
swapIndex = 1; defaultIndex = 1;
break; break;
case RecoveryModel.Simple: case RecoveryModel.Simple:
swapIndex = 2; defaultIndex = 2;
break; break;
default: default:
break; break;
} }
if (swapIndex > 0) options.DefaultValueIndex = defaultIndex;
{
var value = recoveryModels[swapIndex];
recoveryModels.RemoveAt(swapIndex);
recoveryModels.Insert(0, value);
}
} }
return recoveryModels.ToArray(); options.Options = recoveryModels.ToArray();
return options;
} }
private DatabaseFile[] GetDatabaseFiles(Database database) private DatabaseFile[] GetDatabaseFiles(Database database)
@@ -712,9 +708,10 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
/// <summary> /// <summary>
/// Get supported database compatibility levels for this Azure server. /// Get supported database compatibility levels for this Azure server.
/// </summary> /// </summary>
/// <returns>A string array containing the display names of the compatibility levels. This array is empty if the database has a compatibility level we don't recognize.</returns> /// <returns>An <see cref="OptionsCollection"/> of the supported compatibility levels and the default compatibility level's index.</returns>
private string[] GetCompatibilityLevelsAzure(DatabasePrototype prototype) private OptionsCollection GetCompatibilityLevelsAzure(DatabasePrototype prototype)
{ {
var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
// For Azure we loop through all of the possible compatibility levels. We do this because there's only one compat level active on a // For Azure we loop through all of the possible compatibility levels. We do this because there's only one compat level active on a
// version at a time, but that can change at any point so in order to reduce maintenance required when that happens we'll just find // version at a time, but that can change at any point so in order to reduce maintenance required when that happens we'll just find
// the one that matches the current set level and display that // the one that matches the current set level and display that
@@ -723,26 +720,29 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
if (level == prototype.DatabaseCompatibilityLevel) if (level == prototype.DatabaseCompatibilityLevel)
{ {
// Azure can't change the compat level so we only include the current version // Azure can't change the compat level so we only include the current version
return new string[] { displayCompatLevels[level] }; options.Options = new string[] { displayCompatLevels[level] };
return options;
} }
} }
// If we couldn't find the prototype's current compatibility level, then treat compatibillity levels as unsupported for this server // If we couldn't find the prototype's current compatibility level, then treat compatibillity levels as unsupported for this server
return Array.Empty<string>(); return options;
} }
/// <summary> /// <summary>
/// Get supported database compatibility levels for this server. /// Get supported database compatibility levels for this server.
/// </summary> /// </summary>
/// <returns>A string array containing the display names of the compatibility levels. This array is empty if this is either a Sphinx server or if the database has a compatibility level we don't recognize.</returns> /// <returns>An <see cref="OptionsCollection"/> of the supported compatibility levels and the default compatibility level's index.</returns>
private string[] GetCompatibilityLevels(int sqlServerVersion, DatabasePrototype prototype) private OptionsCollection GetCompatibilityLevels(int sqlServerVersion, DatabasePrototype prototype)
{ {
var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
// Unlikely that we are hitting such an old SQL Server, but leaving to preserve // Unlikely that we are hitting such an old SQL Server, but leaving to preserve
// the original semantic of this method. // the original semantic of this method.
if (sqlServerVersion < 8) if (sqlServerVersion < 8)
{ {
// we do not know this version number, we do not know the possible compatibility levels for the server // we do not know this version number, we do not know the possible compatibility levels for the server
return Array.Empty<string>(); return options;
} }
var compatibilityLevels = new List<string>(); var compatibilityLevels = new List<string>();
@@ -809,24 +809,21 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
break; break;
} }
// set the first compatability level for this list based on the prototype // set the default compatability level for this list based on the prototype
for (var i = 0; i < compatibilityLevels.Count; i++) for (var i = 0; i < compatibilityLevels.Count; i++)
{ {
var level = compatibilityLevels[i]; var level = compatibilityLevels[i];
var prototypeLevel = displayCompatLevels[prototype.DatabaseCompatibilityLevel]; var prototypeLevel = displayCompatLevels[prototype.DatabaseCompatibilityLevel];
if (level == prototypeLevel) if (level == prototypeLevel)
{ {
if (i > 0) options.DefaultValueIndex = i;
{ options.Options = compatibilityLevels.ToArray();
compatibilityLevels.RemoveAt(i); return options;
compatibilityLevels.Insert(0, level);
}
return compatibilityLevels.ToArray();
} }
} }
// previous loop did not find the prototype compatibility level in this server's compatability options, so treat compatibility levels as unsupported for this server // previous loop did not find the prototype compatibility level in this server's compatability options, so treat compatibility levels as unsupported for this server
return Array.Empty<string>(); return options;
} }
/// <summary> /// <summary>
@@ -839,16 +836,15 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{ {
if (AzureSqlDbHelper.TryGetServiceObjectiveInfo(edition, out var serviceInfoPair)) if (AzureSqlDbHelper.TryGetServiceObjectiveInfo(edition, out var serviceInfoPair))
{ {
// Move default value to the front of the list var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
var serviceLevelsList = new List<string>(serviceInfoPair.Value); var serviceLevelsList = new List<string>(serviceInfoPair.Value);
var defaultIndex = serviceInfoPair.Key; var defaultIndex = serviceInfoPair.Key;
if (defaultIndex >= 0 && defaultIndex < serviceLevelsList.Count) if (defaultIndex >= 0 && defaultIndex < serviceLevelsList.Count)
{ {
var defaultServiceObjective = serviceLevelsList[defaultIndex]; options.DefaultValueIndex = defaultIndex;
serviceLevelsList.RemoveAt(defaultIndex);
serviceLevelsList.Insert(0, defaultServiceObjective);
} }
var details = new AzureEditionDetails() { EditionDisplayName = edition.DisplayName, Details = serviceLevelsList.ToArray() }; options.Options = serviceLevelsList.ToArray();
var details = new AzureEditionDetails() { EditionDisplayName = edition.DisplayName, EditionOptions = options };
levels.Add(details); levels.Add(details);
} }
else else
@@ -869,16 +865,15 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{ {
if (AzureSqlDbHelper.TryGetDatabaseSizeInfo(edition, out var sizeInfoPair)) if (AzureSqlDbHelper.TryGetDatabaseSizeInfo(edition, out var sizeInfoPair))
{ {
// Move default value to the front of the list var options = new OptionsCollection() { Options = Array.Empty<string>(), DefaultValueIndex = 0 };
var sizeInfoList = new List<DbSize>(sizeInfoPair.Value); var sizeInfoList = new List<DbSize>(sizeInfoPair.Value);
var defaultIndex = sizeInfoPair.Key; var defaultIndex = sizeInfoPair.Key;
if (defaultIndex >= 0 && defaultIndex < sizeInfoList.Count) if (defaultIndex >= 0 && defaultIndex < sizeInfoList.Count)
{ {
var defaultSizeInfo = sizeInfoList[defaultIndex]; options.DefaultValueIndex = defaultIndex;
sizeInfoList.RemoveAt(defaultIndex);
sizeInfoList.Insert(0, defaultSizeInfo);
} }
var details = new AzureEditionDetails() { EditionDisplayName = edition.DisplayName, Details = sizeInfoList.Select(info => info.ToString()).ToArray() }; options.Options = sizeInfoList.Select(info => info.ToString()).ToArray();
var details = new AzureEditionDetails() { EditionDisplayName = edition.DisplayName, EditionOptions = options };
sizes.Add(details); sizes.Add(details);
} }
else else

View File

@@ -9,11 +9,11 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
{ {
public class DatabaseViewInfo : SqlObjectViewInfo public class DatabaseViewInfo : SqlObjectViewInfo
{ {
public string[] LoginNames { get; set; } public OptionsCollection LoginNames { get; set; }
public string[] CollationNames { get; set; } public OptionsCollection CollationNames { get; set; }
public string[] CompatibilityLevels { get; set; } public OptionsCollection CompatibilityLevels { get; set; }
public string[] ContainmentTypes { get; set; } public OptionsCollection ContainmentTypes { get; set; }
public string[] RecoveryModels { get; set; } public OptionsCollection RecoveryModels { get; set; }
public DatabaseFile[] Files { get; set; } public DatabaseFile[] Files { get; set; }
public bool IsAzureDB { get; set; } public bool IsAzureDB { get; set; }
@@ -28,7 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
public class AzureEditionDetails public class AzureEditionDetails
{ {
public string EditionDisplayName { get; set; } public string EditionDisplayName { get; set; }
public string[] Details { get; set; } public OptionsCollection EditionOptions { get; set; }
} }
public class DatabaseFile public class DatabaseFile
@@ -38,4 +38,9 @@ namespace Microsoft.SqlTools.ServiceLayer.ObjectManagement
public string Path { get; set; } public string Path { get; set; }
public string FileGroup { get; set; } public string FileGroup { get; set; }
} }
public class OptionsCollection {
public string[] Options { get; set; }
public int DefaultValueIndex { get; set; }
}
} }

View File

@@ -187,10 +187,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
[Test] [Test]
public void GetAzureServiceLevelObjectivesTest() public void GetAzureServiceLevelObjectivesTest()
{ {
var actualLevelsMap = new Dictionary<string, string[]>(); var actualLevelsMap = new Dictionary<string, OptionsCollection>();
foreach (AzureEditionDetails serviceDetails in DatabaseHandler.AzureServiceLevels) foreach (AzureEditionDetails serviceDetails in DatabaseHandler.AzureServiceLevels)
{ {
actualLevelsMap.Add(serviceDetails.EditionDisplayName, serviceDetails.Details); actualLevelsMap.Add(serviceDetails.EditionDisplayName, serviceDetails.EditionOptions);
} }
var expectedDefaults = new Dictionary<AzureEdition, string>() var expectedDefaults = new Dictionary<AzureEdition, string>()
@@ -209,11 +209,11 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
{ {
var expectedServiceLevels = expectedLevelInfo.Value; var expectedServiceLevels = expectedLevelInfo.Value;
var actualServiceLevels = actualLevelsMap[edition.DisplayName]; var actualServiceLevels = actualLevelsMap[edition.DisplayName];
Assert.That(actualServiceLevels, Is.EquivalentTo(expectedServiceLevels), "Did not get expected SLO list for edition '{0}'", edition.DisplayName); Assert.That(actualServiceLevels.Options, Is.EquivalentTo(expectedServiceLevels), "Did not get expected SLO list for edition '{0}'", edition.DisplayName);
var expectedDefaultIndex = expectedLevelInfo.Key; var expectedDefaultIndex = expectedLevelInfo.Key;
var expectedDefault = expectedServiceLevels[expectedDefaultIndex]; var expectedDefault = expectedServiceLevels[expectedDefaultIndex];
var actualDefault = actualServiceLevels[0]; var actualDefault = actualServiceLevels.Options[actualServiceLevels.DefaultValueIndex];
Assert.That(actualDefault, Is.EqualTo(expectedDefault), "Did not get expected default SLO for edition '{0}'", edition.DisplayName); Assert.That(actualDefault, Is.EqualTo(expectedDefault), "Did not get expected default SLO for edition '{0}'", edition.DisplayName);
} }
else else
@@ -226,10 +226,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
[Test] [Test]
public void GetAzureMaxSizesTest() public void GetAzureMaxSizesTest()
{ {
var actualSizesMap = new Dictionary<string, string[]>(); var actualSizesMap = new Dictionary<string, OptionsCollection>();
foreach (AzureEditionDetails sizeDetails in DatabaseHandler.AzureMaxSizes) foreach (AzureEditionDetails sizeDetails in DatabaseHandler.AzureMaxSizes)
{ {
actualSizesMap.Add(sizeDetails.EditionDisplayName, sizeDetails.Details); actualSizesMap.Add(sizeDetails.EditionDisplayName, sizeDetails.EditionOptions);
} }
var expectedDefaults = new Dictionary<AzureEdition, string>() var expectedDefaults = new Dictionary<AzureEdition, string>()
@@ -248,11 +248,11 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.ObjectManagement
{ {
var expectedSizes = expectedSizeInfo.Value.Select(size => size.ToString()).ToArray(); var expectedSizes = expectedSizeInfo.Value.Select(size => size.ToString()).ToArray();
var actualSizes = actualSizesMap[edition.DisplayName]; var actualSizes = actualSizesMap[edition.DisplayName];
Assert.That(actualSizes, Is.EquivalentTo(expectedSizes), "Did not get expected size list for edition '{0}'", edition.DisplayName); Assert.That(actualSizes.Options, Is.EquivalentTo(expectedSizes), "Did not get expected size list for edition '{0}'", edition.DisplayName);
var expectedDefaultIndex = expectedSizeInfo.Key; var expectedDefaultIndex = expectedSizeInfo.Key;
var expectedDefault = expectedSizes[expectedDefaultIndex]; var expectedDefault = expectedSizes[expectedDefaultIndex];
var actualDefault = actualSizes[0]; var actualDefault = actualSizes.Options[actualSizes.DefaultValueIndex];
Assert.That(actualDefault, Is.EqualTo(expectedDefault.ToString()), "Did not get expected default size for edition '{0}'", edition.DisplayName); Assert.That(actualDefault, Is.EqualTo(expectedDefault.ToString()), "Did not get expected default size for edition '{0}'", edition.DisplayName);
} }
else else