How to trigger a function when a custom field is edited/created in redmine - plugins

I have been researching about this term for so many hours, but I found anything useful yet.
Hope you can help us building this redmine plugin, or provide us some research links to help us find the correct key.
What we want to build
The thing is we want to update one custom field in redmine (let's name it 'Target_CF') whenever another one is created or updated.
We are looking for incrementing possible values of Target_CF, so we can have all custom field's names available to select.
Of course, we want to achieve this without directly editing Redmine's Core, so we thought developing a plugin would be the best approach.
Our plugin also creates and configures a new custom field (the one mentioned above), but I will let this out of the question, because I think it is not relevant for this.
Where we are right now
We have identified some hooks that could be useful for us, as the following:
:controller_custom_fields_new_after_save
:controller_custom_fields_edit_after_save
We have the following directories/files structure so far:
plugins/
custom_plugin/
init.rb
lib/
hooks.rb
The code we have written
init.rb
require_dependency 'hooks'
Redmine::Plugin.register :custom_plugin do
name 'custom_plugin'
author 'author name'
description 'description text'
version '1.0.0'
end
hooks.rb
class Hooks < Redmine::Hook::ViewListener
def controller_custom_fields_edit_after_save(context={ })
#target_custom_field_name = "Target_CF"
CustomField.find_by_name(#target_custom_field_name).possible_values.push(context[:custom_field].name)
end
end
The result of this code is none. I mean, no erros, no updates, nothing at all. There is no change in our possible values after editing/creating another custom field. We are sure there is something we don't know, some concept or workflow, and due to this we are doing something so badly.
Please, help us understeand what we are missing.
Previously we have succesfully developed another plugin that overwrites certain views. So we have kind of little skills in views related plugins, but zero experience at all at controllers ones.
We are using a Redmine 3.2.0 stack by Bitnami and a mysql database.

Well, finally we found out how to extend base controller's methods. I will post it here so hopefully this can be useful to anyone who finds the same doubts we had.
After some more researching, we conclude that it is mandatory to extend base controllers, in order to not directly modify core methods.
This is our final directories/files structures:
plugins/
custom_plugin/
init.rb
lib/
custom_plugin/
issue_custom_field_patch.rb
We previously stated that we could use some hooks to inject our desired functionality, but it seems not to work that way with controllers. On the other hand, we built a patch which will extend target class functionality.
Our final working code
Init.rb
require 'redmine'
ActionDispatch::Callbacks.to_prepare do
require_dependency 'issue_custom_field'
unless IssueCustomField.included_modules.include? CustomPlugin::IssueCustomFieldPatch
IssueCustomField.send(:include, CustomPlugin::IssueCustomFieldPatch)
end
end
Redmine::Plugin.register :custom_plugin do
name 'custom_plugin'
author 'author name'
description 'description text'
version '1.0.0'
end
issue_custom_field_patch.rb
module CustomPlugin
module IssueCustomFieldPatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
after_save :update_possible_values
end
end
end
module ClassMethods
end
module InstanceMethods
def update_possible_values
self.reload
updatedPossibleValues unless self.name == "Target_CF"
end
private
def updatedPossibleValues
#safe_attrs = ['project', 'description', 'due_date', 'category', 'status', 'assigned_to', 'priority', 'fixed_version', 'author', 'lock_version', 'created_on', 'updated_on', 'start_date', 'done_ratio', 'estimated_hours', 'is_private', 'closed_on']
#custom_fields = IssueCustomField.all.select {|cf| !cf[:position].nil?}.collect {|cf| cf.name}
#possible_values = #safe_attrs + #custom_fields
CustomField.find_by_name("Target_CF").update possible_values: #possible_values
end
end
CustomField.send(:include, IssueCustomFieldPatch)
end
Functionality explained
As we stated in the question, we needed to update Target_CF possible values each time the users create/modify/removes a custom field from Redmine.
We extended IssueCustomField's class's instance methods, triggering our new function 'updatedPossibleValues' after each save. This includes creating new custom fields, and of course, updating existing ones and removing them. Because we reload our list of possible values each time, we had to control if its position were nil. If it is, this means that custom field has been removed.
Because of the ultimate action of this patch, which is the updating of another custom field, this also triggered our function, causing an infinite loop. To prevent this, we linked our functionality to every other custom field which name was not 'Target_CF'. A bit rusty fix, but we couldn't find a better approach.
I hope this can be useful to somebody in the future, as they could invest a fraction of time that we spent on this.
Comments, fixes and improvements are very welcome.
Based on: https://www.redmine.org/projects/redmine/wiki/Plugin_Internals which is a bit outdated, but finally could complete the code with help of another resources and forums.

Related

Sparx Enterprise Architect DocumentGenerator does not honour TaggedValues on Stereotype or values from SetProjectConstants and ReplaceField

maybe someone can help me on this. I am trying to generate a document via the DocumentGenerator interface. All in all this works well, except that the DocumentGenerator does not replace the Report Constants with actual values (which are defined on the report package stereotype.
This is the general flow of the document creation code (which generally works):
var gen = Repository.CreateDocumentGenerator();
gen.SetProjectConstant("ReportName", "My Project");
gen.NewDocument(string.Empty);
gen.ReplaceField("ReportName", "My Project");
gen.InsertCoverPageDocument(tags[REPORT_COVERPAGE]);
gen.InsertBreak(DocumentBreak.breakPage);
gen.InsertTOCDocument(tags[REPORT_TOC]);
gen.InsertBreak(DocumentBreak.breakPage);
gen.DocumentPackage((int)nativeId, 0, template);
gen.SaveDocument(fileName, DocumentType.dtDOCX);
I tried ReplaceField and SetProjectConstant both and one at a time before and after calls to NewDocument/InsertCoverPageDocument:
Strangely there is one constant that is being replaced: ReportSummary.
When I run the document generator via "F8" all constants are being replaced correctly.
Other project constants are being replaced correctly.
I can reproduce the behaviour on EA v14.1.1429 and v12.0.1215.
Does someone have a hint for further troubleshooting? Thanks in advance!
========== UPDATE ==========
When I use ReplaceField at the end (before the actual call to SaveDocument the following Report Constants get replaced: {ReportTitle} and {ReportName}
I discovered some workaround: when I manually remove the predefined {Report~} constants from the template and re-add them as Project Constants, their values get replaced correctly.
I will examine this further and give an update as
I did some further investigation on this and came to the following conclusion and workaround (as I have received no comments or answers on this):
I deleted all references to ReportConstants in my EA templates and replaced them by ProjectConstants with the same name.
In my code where I want to generate the documentation I (re)set all ProjectConstants with the actual values via SetProjectConstant and additionally added a call to ReplaceField to replace the constants with the actual values.
The previous mentioned calls are inserted directly before the call to SaveDocument document.
tags.ForEach(t =>
{
if (string.IsNullOrWhiteSpace(t.Key)) return;
generator.SetProjectConstant(t.Key, t.Value);
generator.ReplaceField(t.Key, t.Value);
});
generator.SaveDocument(fileName, DocumentType.dtDOCX);
If someone comes up with a better resonse or explanation for the behaviour I am happy to accept this as answer.
I have also found that when you call ReplaceField on these project constants in a CoverPage template, the formatting defined in the template is overwritten. It seems that some of the SetProjectConstant calls actually set the values as you would expect, and the rest do not.. hence the need to call both sets of APIs.

Protractor Implementation in Angular2 without using ids

I have application in Angularjs2, and developers have not been using ids into it. Now I have to implement the Protractor on same application. Is there anyway to implement the Protractor without using "absolute XPath"?
Thanks in advance!
Please find a huge range of locator-possibilities on the official Protractortest API Page
Every element on a page needs to be uniquely identifiable... else the page wouldn't work, no matter which technology. Therefore with the help of any of the above provided locator-possibilities you'll always find the element you're looking for.
And there is never a need for XPath, except for this only one. (though there is an parentElementArrayFinder introduced in the meantime, so not even that one exception is valid anymore)
UDPATE
If you could use XPath, you can for sure use CSS-Locators.
Here some examples for locators:
$('div.class#id[anyAttribute="anyValue"] div.child.somewhere-below-div-point-class')
element(by.cssContainingText('div[data-index="2"]', 'select this option'))
Or as a specific example the "Learn More" of the "Tree List" section of https://js.devexpress.com/ :
treeListSection = element(by.cssContainingText('div.tab-content h2', 'Tree List')).getDriver();
learnMoreBtn = treeListSection.element(by.cssContainingText('a.tab-button','Learn More'));
learnMoreBtn.click();
Those are just examples, but there is always a way to do it.
If you provide some example-HTML in your Question, I can direct you towards a solution.
UPDATE 2
For getting the Parent Web Element, one could use getDriver() as well

Is it possible to use a control without putting it on a form in VB6?

I'm pretty sure about the answer to this, but I'm trying a variety of things to get a very stubborn project to work. One idea was to try to run code through a control without defining it on a form.
So, for example, my original code looked like this:
frmProcess.MyViewer.MaxPageSize = 100
frmProcess.MyViewer.ResetPages
frmProcess.MyViewer.AddPageToView "C:\TestPage1.txt"
I've changed it to:
Dim objViewer As MyViewer
objViewer.MaxPageSize = 100
objViewer.ResetPages
objViewer.AddPageToView "C:\TestPage1.txt"
I get an error window with "Run-time error '91': Object variable or With block variable not set".
But there doesn't seem to be a way to 'set' this control. Is this just impossible, or is there another way to do it that doesn't require a form?
EDIT: I ended up abandoning this entire path of activity, as an alternate solution was found that got around the problem I was having with this form freezing. I don't want to delete this question in case someone else comes along and can benefit from the answers, which are potentially useful.
Try this on a form.
Dim objViewer As MyViewer
Set objViewer = Controls.Add("MyViewer", "MyViewer1")
objViewer.MaxPageSize = 100
objViewer.ResetPages
objViewer.AddPageToView "C:\TestPage1.txt"
I've had similar situations in the past. If all else fails and you have to use a form you can do something crude like
1) Set the .Left property of the control to a negative number (like -10000) so the control doesn't appear on the form, the user can not see it
2) Make the entire form not visible..
ActiveX controls normally expect a number of services from their containers, for example persistence. They are also "packaged and marked" in ways that set the kinds of instantiation they support.
See Introduction to ActiveX Controls.
While it is perfectly possible for a control to be created in such a way as to make many of the available services optional, most controls are created from template code that requires a number of them. And most controls that are "visible at runtime" are going to require container services.
However that doesn't mean a control can't be designed to support containerless instantiation. A well known example of such a control is Microsoft Script Control 1.0 (MSScriptControl.ScriptControl) which can be used either way.

LibXML: Comment-out a block of Elements

IS there a way to add/initate a comment ( e.g. $dom->createComment ... ) such that it comments out an entire block of xml tags. Basically I want to turn-off the content between the comment.
For example, it would look like this:
<TT>
<AA>keep</AA>
<!-- comment to blocking
<BB>hideme1</BB>
<CC>hideme2</CC>
-->
<DD>d's content is good</DD>
</TT>
Actually this question is a pre-cursor to my attempt to figure-out a method to be able to markup/label/identify the changes to an xml files in support of new client software functionality, but be able to have the ability to remove / back-out these xml changes in the rare event the client needs to fall back to the previous software version (and no I can't just simply point back to the original xml file because the client is allowed to make minor modifications to existing node text values). This is all going to be controlled via a perl script and LibXML's core modules (I can't use modules the client doesn't have).
So basically I've identified three possible types of xml changes as a result of new client sw functionality:
1.) ADD new element node(s) (typically to support new sw functionality)
2.) DELETE element node(s), or blocks of (would be rare, but never-the-less a possibility)
3.) CHANGE node text values (rare, but the new sw may require a new value)
For all three types, the client needs the ability to back out the changes. One thing I was thinking to use is ATTRIBUTES since the existing xml files don't use them. For example, for an ADD change type, I could include an atribute like 'ADD="sw version 4.1"' . This way if it needs to be removed, I could just simply have the perl script find those attribute strings and delete them (using LibXML methods). Same thing with CHANGE change type - I could use an attribute like CHG="newvalue_oldvalue", then again use straight perl (or LibXML) to switch back the value based on the contents of the attribute. The DELETE change type is giving me a problem though (as welll as the others lol!). I want to be able to "keep" the deleted lines in the xml file soley for the purposes if the sw falls back a version (at some late point the perl script could eventually cleanup/delete them).
I know this is a lot, I'm new to LibXML (but not to perl). I was just wonder if any of you have any thoughts as to how to go about it or seen anything resembling this kind of request ... I'd be grateful for any kind of advice! Thank you...

Remove custom code workflow programmatically

For a variety of reasons too convoluted to explain here, I find myself in a position where I need to be able to remove custom code workflows, while leaving the solution there.
I essentially have the same code as the plugin registration tool
Namely
service.Delete("plugintype", new Guid(info));
where info is the workflow id [running on a foreach loop but that's beside the point]
However, while the tool removes the workflows without any issues, my code complains about dependencies.
EM:
Additional information: The PluginType(a0b2dcf7-cf2a-111e-7da9-003021880a42) component cannot be deleted because it is referenced by 1 other components. For a list of referenced components, use the RetrieveDependenciesForDeleteRequest.
which I duly did
RetrieveDependenciesForDeleteRequest req = new RetrieveDependenciesForDeleteRequest();
req.ComponentType = 90; //plugintype
req.ObjectId = new Guid(info);
RetrieveDependenciesForDeleteResponse resp = (RetrieveDependenciesForDeleteResponse)OrgService.Execute(req);
This retrieves an optionvalueset, but there is little I can do with it as I cannot remove it from the solution as the solution is managed.
The only difference I can see is the way the OrganizationServiceProxy gets instantiated. The plugin registration tool includes a way to refresh the securitytoken, but as far as I can tell it's not doing much (I've stepped through the code, but it's possible I missed something)
Are you sure that it is an OptionSetValue that is the dependency? It's much easier to use GUI to determine what the dependencies are. Fire up your solution in CRM 2011, click 'plug-in assemblies', select the relevant assembly then go through each custom workflow/plugin item and click 'Show Dependences'.
In my case I had another workflow (created within CRM) that was referencing a custom workflow preventing removal of the assembly.
You won't be able to remove components from a Managed solution... Are you the author of the solution originally?
As an aside, does your workflow fire on change of the OptionSet that is showing as a dependency?