diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs index e69301ae..7b8790b3 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/ISqlObjectManagerAppService.cs @@ -41,6 +41,11 @@ public interface ISqlObjectManagerAppService : IApplicationService // Database Metadata Operations Task> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName); + + /// + /// Gets the SQL definition/body of a native SQL Server object (Stored Procedure, View, or Function) + /// + Task GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName); // Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy Task SmartSaveAsync(SmartSaveInputDto input); diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlFunctionDto.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlFunctionDto.cs index 785a704a..87c90e24 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlFunctionDto.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlFunctionDto.cs @@ -20,6 +20,7 @@ public class SqlFunctionDto : FullAuditedEntityDto public bool IsDeployed { get; set; } public DateTime? LastDeployedAt { get; set; } public string Parameters { get; set; } + public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object } public class CreateSqlFunctionDto diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlStoredProcedureDto.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlStoredProcedureDto.cs index ca4bf544..70e1894a 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlStoredProcedureDto.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlStoredProcedureDto.cs @@ -18,6 +18,7 @@ public class SqlStoredProcedureDto : FullAuditedEntityDto public bool IsDeployed { get; set; } public DateTime? LastDeployedAt { get; set; } public string Parameters { get; set; } + public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object } public class CreateSqlStoredProcedureDto diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlViewDto.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlViewDto.cs index e8bcb9c4..d0d2aa88 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlViewDto.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application.Contracts/SqlViewDto.cs @@ -18,6 +18,7 @@ public class SqlViewDto : FullAuditedEntityDto public bool IsDeployed { get; set; } public DateTime? LastDeployedAt { get; set; } public bool WithSchemaBinding { get; set; } + public bool IsCustom { get; set; } // true = stored in database, false = native SQL Server object } public class CreateSqlViewDto diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs index ae6f5cf2..ec8cf7c6 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using Erp.SqlQueryManager.Application.Contracts; using Erp.SqlQueryManager.Domain.Entities; @@ -100,67 +102,14 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA }) .ToList(); - // Get all stored procedures for this data source - var procedures = await _procedureRepository.GetListAsync(); - result.StoredProcedures = procedures - .Where(p => p.DataSourceCode == dataSourceCode) - .Select(p => new SqlStoredProcedureDto - { - Id = p.Id, - ProcedureName = p.ProcedureName, - SchemaName = p.SchemaName, - DisplayName = p.DisplayName, - Description = p.Description, - ProcedureBody = p.ProcedureBody, - DataSourceCode = p.DataSourceCode, - Category = p.Category, - Parameters = p.Parameters, - IsDeployed = p.IsDeployed, - LastDeployedAt = p.LastDeployedAt - }) - .ToList(); + // Get all stored procedures for this data source (custom + native merged) + result.StoredProcedures = await GetMergedStoredProceduresAsync(dataSourceCode); - // Get all views for this data source - var views = await _viewRepository.GetListAsync(); - result.Views = views - .Where(v => v.DataSourceCode == dataSourceCode) - .Select(v => new SqlViewDto - { - Id = v.Id, - ViewName = v.ViewName, - SchemaName = v.SchemaName, - DisplayName = v.DisplayName, - Description = v.Description, - ViewDefinition = v.ViewDefinition, - DataSourceCode = v.DataSourceCode, - Category = v.Category, - WithSchemaBinding = v.WithSchemaBinding, - IsDeployed = v.IsDeployed, - LastDeployedAt = v.LastDeployedAt - }) - .ToList(); + // Get all views for this data source (custom + native merged) + result.Views = await GetMergedViewsAsync(dataSourceCode); - // Get all functions for this data source - var functions = await _functionRepository.GetListAsync(); - result.Functions = functions - .Where(f => f.DataSourceCode == dataSourceCode) - .Select(f => new SqlFunctionDto - { - Id = f.Id, - FunctionName = f.FunctionName, - SchemaName = f.SchemaName, - DisplayName = f.DisplayName, - Description = f.Description, - FunctionType = f.FunctionType, - FunctionBody = f.FunctionBody, - ReturnType = f.ReturnType, - DataSourceCode = f.DataSourceCode, - Category = f.Category, - Parameters = f.Parameters, - IsDeployed = f.IsDeployed, - LastDeployedAt = f.LastDeployedAt - }) - .ToList(); + // Get all functions for this data source (custom + native merged) + result.Functions = await GetMergedFunctionsAsync(dataSourceCode); // Get all database tables result.Tables = await GetTablesAsync(dataSourceCode); @@ -214,6 +163,244 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA return tables; } + private async Task> GetMergedStoredProceduresAsync(string dataSourceCode) + { + // Get custom stored procedures from database + var customProcedures = await _procedureRepository.GetListAsync(); + var customList = customProcedures + .Where(p => p.DataSourceCode == dataSourceCode) + .Select(p => new SqlStoredProcedureDto + { + Id = p.Id, + ProcedureName = p.ProcedureName, + SchemaName = p.SchemaName, + DisplayName = p.DisplayName, + Description = p.Description, + ProcedureBody = p.ProcedureBody, + DataSourceCode = p.DataSourceCode, + Category = p.Category, + Parameters = p.Parameters, + IsDeployed = p.IsDeployed, + LastDeployedAt = p.LastDeployedAt, + IsCustom = true + }) + .ToList(); + + // Get native stored procedures from SQL Server + var nativeQuery = @" + SELECT + SCHEMA_NAME(p.schema_id) AS SchemaName, + p.name AS ProcedureName, + p.create_date AS CreatedDate, + p.modify_date AS ModifiedDate + FROM + sys.procedures p + WHERE + p.is_ms_shipped = 0 + ORDER BY + SCHEMA_NAME(p.schema_id), p.name"; + + var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode); + var nativeList = new List(); + + if (result.Success && result.Data != null) + { + foreach (var row in result.Data) + { + var dict = row as System.Collections.Generic.IDictionary; + if (dict != null) + { + var schemaName = dict["SchemaName"]?.ToString() ?? "dbo"; + var procName = dict["ProcedureName"]?.ToString() ?? ""; + + // Skip if already exists in custom list + if (!customList.Any(c => c.SchemaName == schemaName && c.ProcedureName == procName)) + { + // Generate deterministic GUID from schema and name to ensure uniqueness + var uniqueId = GenerateDeterministicGuid($"SP_{dataSourceCode}_{schemaName}_{procName}"); + + nativeList.Add(new SqlStoredProcedureDto + { + Id = uniqueId, + SchemaName = schemaName, + ProcedureName = procName, + DisplayName = $"[{schemaName}].[{procName}]", + DataSourceCode = dataSourceCode, + IsCustom = false, + IsDeployed = true // Native objects are already deployed + }); + } + } + } + } + + // Merge and return + return customList.Concat(nativeList).ToList(); + } + + private async Task> GetMergedViewsAsync(string dataSourceCode) + { + // Get custom views from database + var customViews = await _viewRepository.GetListAsync(); + var customList = customViews + .Where(v => v.DataSourceCode == dataSourceCode) + .Select(v => new SqlViewDto + { + Id = v.Id, + ViewName = v.ViewName, + SchemaName = v.SchemaName, + DisplayName = v.DisplayName, + Description = v.Description, + ViewDefinition = v.ViewDefinition, + DataSourceCode = v.DataSourceCode, + Category = v.Category, + WithSchemaBinding = v.WithSchemaBinding, + IsDeployed = v.IsDeployed, + LastDeployedAt = v.LastDeployedAt, + IsCustom = true + }) + .ToList(); + + // Get native views from SQL Server + var nativeQuery = @" + SELECT + SCHEMA_NAME(v.schema_id) AS SchemaName, + v.name AS ViewName, + v.create_date AS CreatedDate, + v.modify_date AS ModifiedDate + FROM + sys.views v + WHERE + v.is_ms_shipped = 0 + ORDER BY + SCHEMA_NAME(v.schema_id), v.name"; + + var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode); + var nativeList = new List(); + + if (result.Success && result.Data != null) + { + foreach (var row in result.Data) + { + var dict = row as System.Collections.Generic.IDictionary; + if (dict != null) + { + var schemaName = dict["SchemaName"]?.ToString() ?? "dbo"; + var viewName = dict["ViewName"]?.ToString() ?? ""; + + // Skip if already exists in custom list + if (!customList.Any(c => c.SchemaName == schemaName && c.ViewName == viewName)) + { + // Generate deterministic GUID from schema and name to ensure uniqueness + var uniqueId = GenerateDeterministicGuid($"VIEW_{dataSourceCode}_{schemaName}_{viewName}"); + + nativeList.Add(new SqlViewDto + { + Id = uniqueId, + SchemaName = schemaName, + ViewName = viewName, + DisplayName = $"[{schemaName}].[{viewName}]", + DataSourceCode = dataSourceCode, + IsCustom = false, + IsDeployed = true + }); + } + } + } + } + + return customList.Concat(nativeList).ToList(); + } + + private async Task> GetMergedFunctionsAsync(string dataSourceCode) + { + // Get custom functions from database + var customFunctions = await _functionRepository.GetListAsync(); + var customList = customFunctions + .Where(f => f.DataSourceCode == dataSourceCode) + .Select(f => new SqlFunctionDto + { + Id = f.Id, + FunctionName = f.FunctionName, + SchemaName = f.SchemaName, + DisplayName = f.DisplayName, + Description = f.Description, + FunctionType = f.FunctionType, + FunctionBody = f.FunctionBody, + ReturnType = f.ReturnType, + DataSourceCode = f.DataSourceCode, + Category = f.Category, + Parameters = f.Parameters, + IsDeployed = f.IsDeployed, + LastDeployedAt = f.LastDeployedAt, + IsCustom = true + }) + .ToList(); + + // Get native functions from SQL Server + var nativeQuery = @" + SELECT + SCHEMA_NAME(o.schema_id) AS SchemaName, + o.name AS FunctionName, + o.create_date AS CreatedDate, + o.modify_date AS ModifiedDate, + CASE o.type + WHEN 'FN' THEN 'Scalar' + WHEN 'IF' THEN 'InlineTableValued' + WHEN 'TF' THEN 'TableValued' + ELSE 'Unknown' + END AS FunctionType + FROM + sys.objects o + WHERE + o.type IN ('FN', 'IF', 'TF') + AND o.is_ms_shipped = 0 + ORDER BY + SCHEMA_NAME(o.schema_id), o.name"; + + var result = await _sqlExecutorService.ExecuteQueryAsync(nativeQuery, dataSourceCode); + var nativeList = new List(); + + if (result.Success && result.Data != null) + { + foreach (var row in result.Data) + { + var dict = row as System.Collections.Generic.IDictionary; + if (dict != null) + { + var schemaName = dict["SchemaName"]?.ToString() ?? "dbo"; + var funcName = dict["FunctionName"]?.ToString() ?? ""; + + // Skip if already exists in custom list + if (!customList.Any(c => c.SchemaName == schemaName && c.FunctionName == funcName)) + { + var funcTypeStr = dict["FunctionType"]?.ToString() ?? "Scalar"; + var funcType = funcTypeStr == "Scalar" ? SqlFunctionType.ScalarFunction : + funcTypeStr == "InlineTableValued" ? SqlFunctionType.InlineTableValuedFunction : + SqlFunctionType.TableValuedFunction; + + // Generate deterministic GUID from schema and name to ensure uniqueness + var uniqueId = GenerateDeterministicGuid($"FUNC_{dataSourceCode}_{schemaName}_{funcName}"); + + nativeList.Add(new SqlFunctionDto + { + Id = uniqueId, + SchemaName = schemaName, + FunctionName = funcName, + DisplayName = $"[{schemaName}].[{funcName}]", + DataSourceCode = dataSourceCode, + FunctionType = funcType, + IsCustom = false, + IsDeployed = true + }); + } + } + } + } + + return customList.Concat(nativeList).ToList(); + } + #region Query Operations public async Task CreateQueryAsync(CreateSqlQueryDto input) @@ -551,6 +738,34 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA #region Database Metadata Operations + public async Task GetNativeObjectDefinitionAsync(string dataSourceCode, string schemaName, string objectName) + { + ValidateTenantAccess(); + var query = @" + SELECT OBJECT_DEFINITION(OBJECT_ID(@ObjectName)) AS Definition"; + + var fullObjectName = $"[{schemaName}].[{objectName}]"; + var result = await _sqlExecutorService.ExecuteQueryAsync( + query.Replace("@ObjectName", $"'{fullObjectName}'"), + dataSourceCode + ); + + if (result.Success && result.Data != null) + { + var dataList = result.Data.ToList(); + if (dataList.Count > 0) + { + var row = dataList[0] as System.Collections.Generic.IDictionary; + if (row != null && row.ContainsKey("Definition")) + { + return row["Definition"]?.ToString() ?? string.Empty; + } + } + } + + return string.Empty; + } + public async Task> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName) { ValidateTenantAccess(); @@ -838,4 +1053,21 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA } #endregion + + #region Helper Methods + + /// + /// Generates a deterministic GUID from a string input using MD5 hash + /// This ensures same input always produces same GUID + /// + private static Guid GenerateDeterministicGuid(string input) + { + using (var md5 = MD5.Create()) + { + var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input)); + return new Guid(hash); + } + } + + #endregion } diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryManagerAutoMapperProfile.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryManagerAutoMapperProfile.cs index 48c7eadf..d989872a 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryManagerAutoMapperProfile.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlQueryManagerAutoMapperProfile.cs @@ -26,7 +26,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile .Ignore(x => x.LastExecutedAt) .Ignore(x => x.ExecutionCount); - CreateMap(); + CreateMap().Ignore(x => x.IsCustom); CreateMap() .IgnoreFullAuditedObjectProperties() .Ignore(x => x.Id) @@ -34,6 +34,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile .Ignore(x => x.Status) .Ignore(x => x.IsDeployed) .Ignore(x => x.LastDeployedAt); + CreateMap() .IgnoreFullAuditedObjectProperties() .Ignore(x => x.Id) @@ -45,7 +46,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile .Ignore(x => x.IsDeployed) .Ignore(x => x.LastDeployedAt); - CreateMap(); + CreateMap().Ignore(x => x.IsCustom); CreateMap() .IgnoreFullAuditedObjectProperties() .Ignore(x => x.Id) @@ -59,12 +60,12 @@ public class SqlQueryManagerAutoMapperProfile : Profile .Ignore(x => x.TenantId) .Ignore(x => x.ViewName) .Ignore(x => x.SchemaName) - .Ignore(x => x.DataSourceCode) + .Ignore(x => x.DataSourceCode) .Ignore(x => x.Status) .Ignore(x => x.IsDeployed) .Ignore(x => x.LastDeployedAt); - CreateMap(); + CreateMap().Ignore(x => x.IsCustom); CreateMap() .IgnoreFullAuditedObjectProperties() .Ignore(x => x.Id) @@ -79,7 +80,7 @@ public class SqlQueryManagerAutoMapperProfile : Profile .Ignore(x => x.FunctionName) .Ignore(x => x.SchemaName) .Ignore(x => x.FunctionType) - .Ignore(x => x.DataSourceCode) + .Ignore(x => x.DataSourceCode) .Ignore(x => x.Status) .Ignore(x => x.IsDeployed) .Ignore(x => x.LastDeployedAt); diff --git a/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json index ebcbddbd..c0010c48 100644 --- a/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Erp.Platform.DbMigrator/Seeds/LanguagesData.json @@ -10327,6 +10327,12 @@ "tr": "Şablon Kullan", "en": "Use Template" }, + { + "resourceName": "Platform", + "key": "App.Platform.NativeObjectViewOnly", + "tr": "Yerel Nesne Sadece Görüntüle", + "en": "Native Object View Only" + }, { "resourceName": "Platform", "key": "App.Platform.Error", diff --git a/ui/src/proxy/sql-query-manager/models.ts b/ui/src/proxy/sql-query-manager/models.ts index 2f237c04..bbf4395f 100644 --- a/ui/src/proxy/sql-query-manager/models.ts +++ b/ui/src/proxy/sql-query-manager/models.ts @@ -34,6 +34,7 @@ export interface SqlFunctionDto extends FullAuditedEntityDto { isDeployed: boolean lastDeployedAt?: string parameters: string + isCustom: boolean // true = stored in database, false = native SQL Server object } export interface CreateSqlFunctionDto { @@ -132,6 +133,7 @@ export interface SqlStoredProcedureDto extends FullAuditedEntityDto { isDeployed: boolean lastDeployedAt?: string parameters: string + isCustom: boolean // true = stored in database, false = native SQL Server object } export interface CreateSqlStoredProcedureDto { @@ -171,6 +173,7 @@ export interface SqlViewDto extends FullAuditedEntityDto { isDeployed: boolean lastDeployedAt?: string withSchemaBinding: boolean + isCustom: boolean // true = stored in database, false = native SQL Server object } export interface CreateSqlViewDto { diff --git a/ui/src/services/sql-query-manager.service.ts b/ui/src/services/sql-query-manager.service.ts index e89e2699..8ee19a40 100644 --- a/ui/src/services/sql-query-manager.service.ts +++ b/ui/src/services/sql-query-manager.service.ts @@ -185,6 +185,16 @@ export class SqlObjectManagerService { { apiName: this.apiName, ...config }, ) + getNativeObjectDefinition = (dataSourceCode: string, schemaName: string, objectName: string, config?: Partial) => + apiService.fetchData( + { + method: 'GET', + url: '/api/app/sql-object-manager/native-object-definition', + params: { dataSourceCode, schemaName, objectName }, + }, + { apiName: this.apiName, ...config }, + ) + // Smart Save - Analyzes SQL and saves to appropriate table with auto-deploy smartSave = (input: { sqlText: string; dataSourceCode: string; name?: string; description?: string }, config?: Partial) => apiService.fetchData<{ objectType: string; objectId: string; deployed: boolean; message: string }, typeof input>( diff --git a/ui/src/views/sqlQueryManager/SqlQueryManager.tsx b/ui/src/views/sqlQueryManager/SqlQueryManager.tsx index 1e8fb756..47f1d834 100644 --- a/ui/src/views/sqlQueryManager/SqlQueryManager.tsx +++ b/ui/src/views/sqlQueryManager/SqlQueryManager.tsx @@ -113,7 +113,7 @@ const SqlQueryManager = () => { }, []) const handleObjectSelect = useCallback( - (object: SqlObject | null, objectType: SqlObjectType | null) => { + async (object: SqlObject | null, objectType: SqlObjectType | null) => { if (state.isDirty) { if (!confirm(translate('::App.Platform.UnsavedChangesConfirmation'))) { return @@ -124,16 +124,76 @@ const SqlQueryManager = () => { if (object) { if (objectType === 1) { // Query - content = (object as SqlQueryDto).queryText + content = (object as SqlQueryDto).queryText || '' } else if (objectType === 2) { // Stored Procedure - content = (object as SqlStoredProcedureDto).procedureBody + const sp = object as SqlStoredProcedureDto + if (sp.isCustom) { + content = sp.procedureBody || '' + } else { + // Native SP - fetch definition from SQL Server + try { + const result = await sqlObjectManagerService.getNativeObjectDefinition( + state.selectedDataSource || '', + sp.schemaName, + sp.procedureName + ) + content = result.data || '' + } catch (error) { + toast.push( + + {translate('::App.Platform.FailedToLoadDefinition')} + , + { placement: 'top-center' }, + ) + } + } } else if (objectType === 3) { // View - content = (object as SqlViewDto).viewDefinition + const view = object as SqlViewDto + if (view.isCustom) { + content = view.viewDefinition || '' + } else { + // Native View - fetch definition from SQL Server + try { + const result = await sqlObjectManagerService.getNativeObjectDefinition( + state.selectedDataSource || '', + view.schemaName, + view.viewName + ) + content = result.data || '' + } catch (error) { + toast.push( + + {translate('::App.Platform.FailedToLoadDefinition')} + , + { placement: 'top-center' }, + ) + } + } } else if (objectType === 4) { // Function - content = (object as SqlFunctionDto).functionBody + const func = object as SqlFunctionDto + if (func.isCustom) { + content = func.functionBody || '' + } else { + // Native Function - fetch definition from SQL Server + try { + const result = await sqlObjectManagerService.getNativeObjectDefinition( + state.selectedDataSource || '', + func.schemaName, + func.functionName + ) + content = result.data || '' + } catch (error) { + toast.push( + + {translate('::App.Platform.FailedToLoadDefinition')} + , + { placement: 'top-center' }, + ) + } + } } } @@ -148,7 +208,7 @@ const SqlQueryManager = () => { isSaved: false, })) }, - [state.isDirty, translate], + [state.isDirty, state.selectedDataSource, translate], ) const handleEditorChange = useCallback((value: string | undefined) => { @@ -345,7 +405,7 @@ GO`, const handleUseTemplateFromDialog = useCallback( (templateContent: string, templateType: string) => { // Check if editor has content - const hasUserContent = state.editorContent.trim() && state.isDirty + const hasUserContent = state.editorContent?.trim() && state.isDirty if (hasUserContent) { // Ask for confirmation @@ -365,7 +425,7 @@ GO`, const templateContent = template || getTemplateContent(templateType) // Check if editor has content and it's not from a previous template - const hasUserContent = state.editorContent.trim() && state.isDirty + const hasUserContent = state.editorContent?.trim() && state.isDirty if (hasUserContent) { // Ask for confirmation @@ -405,7 +465,7 @@ GO`, // Seçili text varsa onu, yoksa tüm editor içeriğini kullan const selectedText = editorRef.current?.getSelectedText() || '' - const queryToExecute = selectedText.trim() || state.editorContent.trim() + const queryToExecute = selectedText.trim() || state.editorContent?.trim() || '' if (!queryToExecute) { toast.push( @@ -471,7 +531,7 @@ GO`, return } - if (!state.editorContent.trim()) { + if (!state.editorContent?.trim()) { toast.push( {translate('::App.Platform.PleaseEnterContentToSave')} @@ -771,7 +831,7 @@ GO`, onClick={handleSave} disabled={ !state.selectedDataSource || - !state.editorContent.trim() || + !state.editorContent?.trim() || (state.isSaved && !state.isDirty) || !state.executionResult?.success } diff --git a/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx b/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx index afb191b3..cac6a987 100644 --- a/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx +++ b/ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx @@ -120,9 +120,10 @@ const SqlObjectExplorer = ({ const deployInfo = p.isDeployed && p.lastDeployedAt ? ` (${new Date(p.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})` : ''; + const customBadge = p.isCustom ? ' 📝' : ' 🗄️'; return { - id: p.id || '', - label: `${p.displayName || p.procedureName}${p.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`, + id: p.id!, + label: `${p.displayName || p.procedureName}${customBadge}${p.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`, type: 'object' as const, objectType: 2 as SqlObjectType, data: p, @@ -140,9 +141,10 @@ const SqlObjectExplorer = ({ const deployInfo = v.isDeployed && v.lastDeployedAt ? ` (${new Date(v.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})` : ''; + const customBadge = v.isCustom ? ' 📝' : ' 🗄️'; return { - id: v.id || '', - label: `${v.displayName || v.viewName}${v.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`, + id: v.id!, + label: `${v.displayName || v.viewName}${customBadge}${v.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`, type: 'object' as const, objectType: 3 as SqlObjectType, data: v, @@ -160,9 +162,10 @@ const SqlObjectExplorer = ({ const deployInfo = f.isDeployed && f.lastDeployedAt ? ` (${new Date(f.lastDeployedAt).toLocaleString('tr-TR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })})` : ''; + const customBadge = f.isCustom ? ' 📝' : ' 🗄️'; return { - id: f.id || '', - label: `${f.displayName || f.functionName}${f.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`, + id: f.id!, + label: `${f.displayName || f.functionName}${customBadge}${f.isDeployed ? ' ✅' : ' ❌'}${deployInfo}`, type: 'object' as const, objectType: 4 as SqlObjectType, data: f, @@ -289,7 +292,12 @@ const SqlObjectExplorer = ({ // Column clicked - do nothing or show info return } else if (node.type === 'object' && node.data) { - if (node.objectType) { + // Check if it's a table - generate SELECT template + if (node.id.startsWith('table-') && onTemplateSelect) { + const table = node.data as any + const selectQuery = `SELECT TOP 10 * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`};` + onTemplateSelect(selectQuery, 'table-select') + } else if (node.objectType) { onObjectSelect(node.data, node.objectType) } } @@ -498,7 +506,7 @@ const SqlObjectExplorer = ({ className="fixed z-50 bg-white dark:bg-gray-800 shadow-lg rounded border border-gray-200 dark:border-gray-700 py-1" style={{ top: contextMenu.y, left: contextMenu.x }} > - {contextMenu.node?.type === 'object' && contextMenu.node?.objectType && ( + {contextMenu.node?.type === 'object' && contextMenu.node?.objectType && contextMenu.node?.data?.isCustom && ( <>