-
-
Save tlanzer-aktion/99ff743dff939b4c815aefdbbe48847f to your computer and use it in GitHub Desktop.
README ProjectList Graph Extension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Web; | |
using PX.Data; | |
using PX.Data.BQL.Fluent; | |
using PX.Data.BQL; | |
using PX.SM; | |
using Aktion.Common.Acumatica.ReadMe.DAC; | |
using System.Web.Caching; | |
namespace Aktion.Common.Acumatica.ReadMe.Graphs | |
{ | |
/// <summary> | |
/// ProjectList graph extension for Customization Projects screen | |
/// </summary> | |
public class ProjectListExt : PXGraphExtension<ProjectList> | |
{ | |
#region Constants | |
protected const string README_TEMPLATE = | |
"Name: {0}" | |
+ "\r\nDescription: {1}" | |
+ "\r\nAuthor: " | |
+ "\r\n" | |
+ "\r\nINSTALLATION" | |
+ "\r\n1) Import package.zip file" | |
+ "\r\n2) Publish package" | |
+ "\r\n" | |
+ "\r\nFEATURES" | |
+ "\r\n" | |
+ "\r\nREFERENCES" | |
+ "\r\n" | |
; | |
#endregion | |
#region Views | |
/// <summary> | |
/// Current project view for use in read.me dialog | |
/// </summary> | |
public SelectFrom<CustProject> | |
.Where<CustProject.projID.IsNotNull | |
.And<CustProject.projID.IsEqual<CustProject.projID.FromCurrent>>>.View ReadMeProject; | |
/// <summary> | |
/// Custom objects view for current project | |
/// </summary> | |
public SelectFrom<CustObject> | |
.Where<CustObject.projectID.IsEqual<CustProject.projID.FromCurrent>>.View ProjectObjects; | |
/// <summary> | |
/// File reference header view for searching for read.me file name | |
/// </summary> | |
public SelectFrom<UploadFile> | |
.Where<UploadFile.name.Contains<@P.AsString>>.View ProjectFiles; | |
/// <summary> | |
/// File reference revision view for searching for read.me file | |
/// </summary> | |
public SelectFrom<UploadFileRevision> | |
.Where<UploadFileRevision.fileID.IsEqual<@P.AsGuid>> | |
.OrderBy<UploadFileRevision.fileRevisionID.Desc>.View ProjectFileRevisions; | |
#endregion | |
#region Actions | |
/// <summary> | |
/// read.me toolbar action button | |
/// </summary> | |
public PXAction<CustProject> openReadMe; | |
/// <summary> | |
/// Action method for read.me button. Specify its display settings. The actual dialog popup is specified in the UI for the screen. | |
/// </summary> | |
/// <param name="adapter"></param> | |
/// <returns></returns> | |
[PXButton(DisplayOnMainToolbar = true)] | |
[PXUIField(DisplayName = "read.me")] | |
public IEnumerable OpenReadMe(PXAdapter adapter) | |
{ | |
CustProject project = Base.Projects.Current; | |
if (project == null) return adapter.Get(); | |
CustProjectExt projectExt = project.GetExtension<CustProjectExt>(); | |
if (string.IsNullOrEmpty(projectExt.UsrReadMe)) | |
{ | |
// If read.me file exists, open and set field content | |
if (ReadMeFileExists(project)) | |
{ | |
projectExt.UsrReadMe = GetReadMeContentText(project); | |
} | |
// If file doesn't exist, default with a read.me template | |
else | |
{ | |
projectExt.UsrReadMe = string.Format(README_TEMPLATE, project.Name, project.Description); | |
} | |
} | |
return adapter.Get(); | |
} | |
/// <summary> | |
/// read.me dialog Save button | |
/// </summary> | |
public PXAction<CustProject> ReadMeSave; | |
/// <summary> | |
/// Action method for Save button. Save the read.me contents to file. | |
/// </summary> | |
[PXButton(DisplayOnMainToolbar = false)] | |
[PXUIField(DisplayName = "Save")] | |
protected void readMeSave() | |
{ | |
Base.Projects.UpdateCurrent(); | |
// Write/update read.me file to App_Data folder | |
SaveReadMeToFile(); | |
// Add/update row to CustObject table | |
SaveReadMeToDB(); | |
Base.Persist(); | |
} | |
/// <summary> | |
/// read.me dialog Cancel button | |
/// </summary> | |
public PXAction<CustProject> ReadMeCancel; | |
[PXButton(DisplayOnMainToolbar = false)] | |
[PXUIField(DisplayName = "Cancel")] | |
protected void readMeCancel() | |
{ | |
Base.Projects.Cache.Clear(); | |
Base.Projects.ClearDialog(); | |
} | |
#endregion | |
#region Event handlers | |
/// <summary> | |
/// CustProject RowSelected event handler | |
/// </summary> | |
protected virtual void CustProject_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected baseMethod) | |
{ | |
baseMethod?.Invoke(cache, e); | |
var project = (CustProject)e.Row; | |
if (project == null) return; | |
CustProjectExt projectExt = project.GetExtension<CustProjectExt>(); | |
if (string.IsNullOrEmpty(projectExt.UsrReadMe)) | |
{ | |
// If read.me file exists, open and set field content, otherwise default with a template | |
if (ReadMeFileExists(project)) | |
{ | |
projectExt.UsrReadMe = GetReadMeContentText(project); | |
} | |
else | |
{ | |
projectExt.UsrReadMe = string.Format(README_TEMPLATE, project.Name, project.Description); | |
} | |
} | |
} | |
#endregion | |
#region Local methods | |
/// <summary> | |
/// Determine if a read.me file exists for the project | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <returns>whether read.me file exists</returns> | |
protected bool ReadMeFileExists(CustProject project) | |
{ | |
// Look for the project read.me file in the project package file references | |
string filePath = GetProjectReadMePath(project); | |
return File.Exists(filePath); | |
} | |
/// <summary> | |
/// Build the path of a project's read.me file | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <returns>read.me file path</returns> | |
protected string GetProjectReadMePath(CustProject project) | |
{ | |
return string.Format(@"{0}\{1}_read.me", HttpContext.Current.Server.MapPath("~/App_Data"), project.Name); | |
} | |
/// <summary> | |
/// Build the read.me path for its project object | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <returns>read.me project object path</returns> | |
protected string GetObjectReadMePath(CustProject project) | |
{ | |
return string.Format(@"File#App_Data\{0}_read.me", project.Name); | |
} | |
/// <summary> | |
/// Build the read.me project object content field | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <param name="fileID">file object ID</param> | |
/// <returns>read.me project object content</returns> | |
protected string GetObjectReadMeContent(CustProject project, Guid fileID) | |
{ | |
return string.Format("<File AppRelativePath=\"App_Data\\{0}_read.me\" FileID=\"{1}\" />", project.Name, fileID); | |
} | |
/// <summary> | |
/// Build the read.me path for its upload file reference | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <returns>read.me project object path</returns> | |
protected string GetUploadFileReadMePath(CustProject project) | |
{ | |
return string.Format(@"\CstFile-{0}-App_Data-{1}_read.me", project.Name, project.Name); | |
} | |
/// <summary> | |
/// Pull text content out of the project read.me file | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <returns>read.me file content</returns> | |
protected string GetReadMeContentText(CustProject project) | |
{ | |
string filePath = GetProjectReadMePath(project); | |
return File.ReadAllText(filePath); | |
} | |
/// <summary> | |
/// Pull content as binary out of the project read.me file | |
/// </summary> | |
/// <param name="project">project data row</param> | |
/// <returns>read.me file content</returns> | |
protected byte[] GetReadMeContentBytes(CustProject project) | |
{ | |
string filePath = GetProjectReadMePath(project); | |
return File.ReadAllBytes(filePath); | |
} | |
/// <summary> | |
/// Save customization project read.me content to the file system to be included in its package | |
/// </summary> | |
internal void SaveReadMeToFile() | |
{ | |
CustProject project = Base.Projects.Current; | |
if (project == null) return; | |
CustProjectExt projectExt = project.GetExtension<CustProjectExt>(); | |
// Write the read.me content to the web server file system. The read.me file path includes | |
// the project name. | |
File.WriteAllText(GetProjectReadMePath(project), projectExt.UsrReadMe); | |
} | |
/// <summary> | |
/// Save customization project read.me file as a file object for a project in order to include | |
/// in its package | |
/// </summary> | |
internal void SaveReadMeToDB() | |
{ | |
CustProject project = Base.Projects.Current; | |
if (project == null) return; | |
// Look for the project read.me file in the project package file references | |
string objFileName = GetObjectReadMePath(project); | |
CustObject fileObject = ProjectObjects.Select().RowCast<CustObject>().FirstOrDefault(o => o.Name == objFileName); | |
// Search for the file reference according to its special Acumatica-formatted and delimited name | |
string uploadFileName = GetUploadFileReadMePath(project); | |
UploadFile uploadFile = ProjectFiles.SelectSingle(uploadFileName); | |
UploadFileRevision uploadFileRev = ProjectFileRevisions.SelectSingle(uploadFile?.FileID); | |
// Read file into memory | |
byte[] readMeFile = GetReadMeContentBytes(project); | |
// Save to the db | |
using (var tran = new PXTransactionScope()) | |
{ | |
Guid objectID = fileObject == null ? Guid.NewGuid() : fileObject.ObjectID.Value; | |
Guid fileID = uploadFile == null ? Guid.NewGuid() : uploadFile.FileID.Value; | |
// If the file object is not referenced by the customization, add the object | |
if (fileObject == null) | |
{ | |
PXDatabase.Insert<CustObject>( | |
new PXDataFieldAssign(nameof(CustObject.objectID), PXDbType.UniqueIdentifier, objectID), | |
new PXDataFieldAssign(nameof(CustObject.name), PXDbType.NVarChar, objFileName), | |
new PXDataFieldAssign(nameof(CustObject.type), PXDbType.NVarChar, "File"), | |
new PXDataFieldAssign(nameof(CustObject.projectID), PXDbType.UniqueIdentifier, project.ProjID), | |
new PXDataFieldAssign(nameof(CustObject.content), PXDbType.NVarChar, GetObjectReadMeContent(project, fileID)), | |
new PXDataFieldAssign(nameof(CustObject.description), PXDbType.NVarChar, string.Format("{0} README file", project.Name)), | |
new PXDataFieldAssign(nameof(CustObject.isDisabled), PXDbType.Bit, false), | |
new PXDataFieldAssign(nameof(CustObject.createdByID), PXDbType.UniqueIdentifier, Base.Accessinfo.UserID), | |
new PXDataFieldAssign(nameof(CustObject.createdDateTime), PXDbType.DateTime, DateTime.Now), | |
new PXDataFieldAssign(nameof(CustObject.lastModifiedByID), PXDbType.UniqueIdentifier, Base.Accessinfo.UserID), | |
new PXDataFieldAssign(nameof(CustObject.lastModifiedDateTime), PXDbType.DateTime, DateTime.Now), | |
new PXDataFieldAssign(nameof(CustObject.noteID), PXDbType.UniqueIdentifier, Guid.NewGuid()) | |
); | |
} | |
// If the file reference doesn't exist, add the file | |
if (uploadFile == null) | |
{ | |
PXDatabase.Insert<UploadFile>( | |
new PXDataFieldAssign(nameof(UploadFile.fileID), PXDbType.UniqueIdentifier, fileID), | |
new PXDataFieldAssign(nameof(UploadFile.name), PXDbType.NVarChar, string.Format(@"Files ({0}){1}", objectID, uploadFileName)), | |
new PXDataFieldAssign(nameof(UploadFile.versioned), PXDbType.Bit, false), | |
new PXDataFieldAssign(nameof(UploadFile.lastRevisionID), PXDbType.Int, 1), | |
//new PXDataFieldAssign(nameof(UploadFile.primaryScreenID), PXDbType.VarChar, "SM000000"), | |
new PXDataFieldAssign(nameof(UploadFile.isPublic), PXDbType.Bit, false), | |
new PXDataFieldAssign(nameof(UploadFile.isAccessRightsFromEntities), PXDbType.Bit, true), | |
new PXDataFieldAssign(nameof(UploadFile.createdByID), PXDbType.UniqueIdentifier, Base.Accessinfo.UserID), | |
new PXDataFieldAssign(nameof(UploadFile.createdDateTime), PXDbType.DateTime, DateTime.Now), | |
new PXDataFieldAssign(nameof(UploadFile.noteID), PXDbType.UniqueIdentifier, Guid.NewGuid()) | |
); | |
} | |
// If the file revision doesn't exist, add it | |
if (uploadFileRev == null) | |
{ | |
PXDatabase.Insert<UploadFileRevision>( | |
new PXDataFieldAssign(nameof(UploadFileRevision.fileID), PXDbType.UniqueIdentifier, fileID), | |
new PXDataFieldAssign(nameof(UploadFileRevision.fileRevisionID), PXDbType.Int, 1), | |
new PXDataFieldAssign(nameof(UploadFileRevision.Data), PXDbType.VarBinary, readMeFile), | |
new PXDataFieldAssign(nameof(UploadFileRevision.size), PXDbType.Int, readMeFile.Length), | |
new PXDataFieldAssign(nameof(UploadFileRevision.createdByID), PXDbType.UniqueIdentifier, Base.Accessinfo.UserID), | |
new PXDataFieldAssign(nameof(UploadFileRevision.createdDateTime), PXDbType.DateTime, DateTime.Now) | |
); | |
} | |
// If the file reference exists, update the file | |
else if (uploadFileRev != null) | |
{ | |
PXDatabase.Update<UploadFileRevision>( | |
new PXDataFieldRestrict(nameof(UploadFileRevision.fileID), uploadFileRev.FileID), | |
new PXDataFieldRestrict(nameof(UploadFileRevision.fileRevisionID), uploadFileRev.FileRevisionID), | |
new PXDataFieldAssign(nameof(UploadFileRevision.Data), PXDbType.VarBinary, readMeFile), | |
new PXDataFieldAssign(nameof(UploadFileRevision.size), PXDbType.Int, readMeFile.Length) | |
); | |
} | |
tran.Complete(); | |
} | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See article at https://community.acumatica.com/develop-customizations-288/readme-for-customization-projects-by-tony-lanzer-18318 for context.
Using a README for an Acumatica customization project is effective in documenting and tracking its purpose and features; and including it within the customization project allows it to be easily referenced and reviewed. The ultimate goal then is to package a README file in a customization project’s packaged .zip file.
To support the creation of README files within a customization project where you (1) don’t have access to the application website folders, or (2) want to create the file within Acumatica; you can create a customization project like I did that accomplishes this.