Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

generate README from POD

  • Loading branch information...
commit 7323bbf8fd70f3ba37888be21c6430ea81487ab1 1 parent 376263e
Ruslan Zakirov authored March 13, 2012
1  MANIFEST
@@ -40,3 +40,4 @@ t/due.t
40 40
 t/queue.t
41 41
 t/reporting/basic.t
42 42
 t/starts.t
  43
+README
1  Makefile.PL
@@ -3,6 +3,7 @@ use inc::Module::Install;
3 3
 abstract('Service Level Agreements for RT');
4 4
 RTx ('RT-Extension-SLA');
5 5
 all_from('lib/RT/Extension/SLA.pm');
  6
+readme_from('lib/RT/Extension/SLA.pm');
6 7
 license('gpl2');
7 8
 
8 9
 build_requires('Test::More');
351  README
... ...
@@ -0,0 +1,351 @@
  1
+NAME
  2
+    RT::Extension::SLA - Service Level Agreements for RT
  3
+
  4
+DESCRIPTION
  5
+    RT extension to implement automated due dates using service levels.
  6
+
  7
+UPGRADING
  8
+    On upgrade you shouldn't run 'make initdb'.
  9
+
  10
+    If you were using 0.02 or older version of this extension with RT 3.8.1
  11
+    then you have to uninstall that manually. List of files you can find in
  12
+    the MANIFEST.
  13
+
  14
+INSTALL
  15
+    perl Makefile.PL
  16
+    make
  17
+    make install
  18
+    make initdb (for the first time only)
  19
+    Base configuration
  20
+        In RT 3.8 and later, you must enable the plugin by adding
  21
+        RT::Extension::SLA to your @Plugins line (or create one) like:
  22
+
  23
+            Set(@Plugins,(qw(RT::Extension::SLA)));
  24
+
  25
+CONFIGURATION
  26
+    Service level agreements of tickets is controlled by an SLA custom field
  27
+    (CF). This field is created during "make initdb" step (above) and
  28
+    applied globally. This CF MUST be of "select one value" type. Values of
  29
+    the CF define the service levels.
  30
+
  31
+    It's possible to define different set of levels for different queues.
  32
+    You can create several CFs with the same name and different set of
  33
+    values. But if you move tickets between queues a lot then it's going to
  34
+    be a problem and it's preferred to use ONE SLA custom field.
  35
+
  36
+    There is no WebUI in the current version. Almost everything is
  37
+    controlled in the RT's config using option %RT::ServiceAgreements and
  38
+    %RT::ServiceBusinessHours. For example:
  39
+
  40
+        Set( %ServiceAgreements,
  41
+            Default => '4h',
  42
+            QueueDefault => {
  43
+                'Incident' => '2h',
  44
+            },
  45
+            Levels => {
  46
+                '2h' => { Resolve => { RealMinutes => 60*2 } },
  47
+                '4h' => { Resolve => { RealMinutes => 60*4 } },
  48
+            },
  49
+        );
  50
+
  51
+    In this example *Incident* is the name of the queue, and *2h* is the
  52
+    name of the SLA which will be applied to this queue by default.
  53
+
  54
+    Each service level can be described using several options:
  55
+    StartImmediately, Resolve, Response, KeepInLoop, OutOfHours and
  56
+    ServiceBusinessHours.
  57
+
  58
+  StartImmediately (boolean, false)
  59
+    By default when a ticket is created Starts date is set to first business
  60
+    minute after time of creation. In other words if a ticket is created
  61
+    during business hours then Starts will be equal to Created time,
  62
+    otherwise Starts will be beginning of the next business day.
  63
+
  64
+    However, if you provide 24/7 support then you most probably would be
  65
+    interested in Starts to be always equal to Created time. In this case
  66
+    you can set option StartImmediately to a true value.
  67
+
  68
+    Example:
  69
+
  70
+        '24/7' => {
  71
+            StartImmediately => 1,
  72
+            Response => { RealMinutes => 30 },
  73
+        },
  74
+        'standard' => {
  75
+            StartImmediately => 0, # can be ommited as it's default
  76
+            Response => { BusinessMinutes => 2*60 },
  77
+        },
  78
+
  79
+  Resolve and Response (interval, no defaults)
  80
+    These two options define deadlines for resolve of a ticket and reply to
  81
+    customer(requestors) questions accordingly.
  82
+
  83
+    You can define them using real time, business or both. Read more about
  84
+    the latter below.
  85
+
  86
+    The Due date field is used to store calculated deadlines.
  87
+
  88
+   Resolve
  89
+    Defines deadline when a ticket should be resolved. This option is quite
  90
+    simple and straightforward when used without "Response".
  91
+
  92
+    Example:
  93
+
  94
+        # 8 business hours
  95
+        'simple' => { Resolve => 60*8 },
  96
+        ...
  97
+        # one real week
  98
+        'hard' => { Resolve => { RealMinutes => 60*24*7 } },
  99
+
  100
+   Response
  101
+    In many companies providing support service(s) resolve time of a ticket
  102
+    is less important than time of response to requestors from stuff
  103
+    members.
  104
+
  105
+    You can use Response option to define such deadlines. When you're using
  106
+    this option Due time "flips" when requestors and non-requestors reply to
  107
+    a ticket. We set Due date when a ticket is created, unset when
  108
+    non-requestor replies... until ticket is closed when ticket's Due date
  109
+    is also unset.
  110
+
  111
+    NOTE that behaviour changes when Resolve and Response options are
  112
+    combined, read below.
  113
+
  114
+    As response deadlines are calculated using requestors' activity so
  115
+    several rules applies to make things sane:
  116
+
  117
+    *   If requestor(s) reply multiple times and are ignored then the
  118
+        deadline is calculated using the oldest requestors' correspondence.
  119
+
  120
+    *   If a ticket has no requestor(s) then it has no response deadline.
  121
+
  122
+    *   If a ticket is created by non-requestor then due date is left unset.
  123
+
  124
+    *   If owner of a ticket is its requestor then his actions are treated
  125
+        as non-requestors'.
  126
+
  127
+   Using both Resolve and Response in the same level
  128
+    Resolve and Response can be combined. In such case due date is set
  129
+    according to the earliest of two deadlines and never is dropped to 'not
  130
+    set'.
  131
+
  132
+    If a ticket met its Resolve deadline then due date stops "flipping", is
  133
+    freezed and the ticket becomes overdue. Before that moment when
  134
+    non-requestor replies to a ticket, due date is changed to Resolve
  135
+    deadline instead of 'Not Set', as well this happens when a ticket is
  136
+    closed. So all the time due date is defined.
  137
+
  138
+    Example:
  139
+
  140
+        'standard delivery' => {
  141
+            Response => { RealMinutes => 60*1  }, # one hour
  142
+            Resolve  => { RealMinutes => 60*24 }, # 24 real hours
  143
+        },
  144
+
  145
+    A client orders goods and due date of the order is set to the next one
  146
+    hour, you have this hour to process the order and write a reply. As soon
  147
+    as goods are delivered you resolve tickets and usually meet Resolve
  148
+    deadline, but if you don't resolve or user replies then most probably
  149
+    there are problems with delivery of the goods. And if after a week you
  150
+    keep replying to the client and always meeting one hour response
  151
+    deadline that doesn't mean the ticket is not over due. Due date was
  152
+    frozen 24 hours after creation of the order.
  153
+
  154
+   Using business and real time in one option
  155
+    It's quite rare situation when people need it, but we've decided that
  156
+    business is applied first and then real time when deadline described
  157
+    using both types of time. For example:
  158
+
  159
+        'delivery' => {
  160
+            Resolve => { BusinessMinutes => 0, RealMinutes => 60*8 },
  161
+        },
  162
+        'fast delivery' {
  163
+            StartImmediately => 1,
  164
+            Resolve => { RealMinutes => 60*8 },
  165
+        },
  166
+
  167
+    For delivery requests which come into the system during business hours
  168
+    these levels define the same deadlines, otherwise the first level set
  169
+    deadline to 8 real hours starting from the next business day, when
  170
+    tickets with the second level should be resolved in the next 8 hours
  171
+    after creation.
  172
+
  173
+  Keep in loop (interval, no defaults)
  174
+    If response deadline is used then Due date is changed to repsonse
  175
+    deadline or to "Not Set" when staff replies to a ticket. In some cases
  176
+    you want to keep requestors in loop and keed them up to date every few
  177
+    hours. KeepInLoop option can be used to achieve this.
  178
+
  179
+        'incident' => {
  180
+            Response   => { RealMinutes => 60*1  }, # one hour
  181
+            KeepInLoop => { RealMinutes => 60*2 }, # two hours
  182
+            Resolve    => { RealMinutes => 60*24 }, # 24 real hours
  183
+        },
  184
+
  185
+    In the above example Due is set to one hour after creation, reply of a
  186
+    non-requestor moves Due date two hours forward, requestors' replies move
  187
+    Due date to one hour and resolve deadine is 24 hours.
  188
+
  189
+  OutOfHours (struct, no default)
  190
+    Out of hours modifier. Adds more real or business minutes to resolve
  191
+    and/or reply options if event happens out of business hours, read also
  192
+    </"Configuring business hours"> below.
  193
+
  194
+    Example:
  195
+
  196
+        'level x' => {
  197
+            OutOfHours => { Resolve => { RealMinutes => +60*24 } },
  198
+            Resolve    => { RealMinutes => 60*24 },
  199
+        },
  200
+
  201
+    If a request comes into the system during night then supporters have two
  202
+    hours, otherwise only one.
  203
+
  204
+        'level x' => {
  205
+            OutOfHours => { Response => { BusinessMinutes => +60*2 } },
  206
+            Resolve    => { BusinessMinutes => 60 },
  207
+        },
  208
+
  209
+    Supporters have two additional hours in the morning to deal with bunch
  210
+    of requests that came into the system during the last night.
  211
+
  212
+  Configuring business hours
  213
+    In the config you can set one or more work schedules. Use the following
  214
+    format:
  215
+
  216
+        Set( %ServiceBusinessHours,
  217
+            'Default' => {
  218
+                ... description ...
  219
+            },
  220
+            'Support' => {
  221
+                ... description ...
  222
+            },
  223
+            'Sales' => {
  224
+                ... description ...
  225
+            },
  226
+        );
  227
+
  228
+    Read more about how to describe a schedule in Business::Hours.
  229
+
  230
+   Defining different business hours for service levels
  231
+    Each level supports BusinessHours option to specify your own business
  232
+    hours.
  233
+
  234
+        'level x' => {
  235
+            BusinessHours => 'work just in Monday',
  236
+            Resolve    => { BusinessMinutes => 60 },
  237
+        },
  238
+
  239
+    then %RT::ServiceBusinessHours should have the corresponding definition:
  240
+
  241
+        Set( %ServiceBusinessHours,
  242
+            'work just in Monday' => {
  243
+                1 => { Name => 'Monday', Start => '9:00', End => '18:00' },
  244
+            },
  245
+        );
  246
+
  247
+    Default Business Hours setting is in
  248
+    $RT::ServiceBusinessHours{'Default'}.
  249
+
  250
+  Defining service levels per queue
  251
+    In the config you can set per queue defaults, using:
  252
+
  253
+        Set( %ServiceAgreements,
  254
+            Default => 'global default level of service',
  255
+            QueueDefault => {
  256
+                'queue name' => 'default value for this queue',
  257
+                ...
  258
+            },
  259
+            ...
  260
+        );
  261
+
  262
+  Access control
  263
+    You can totally hide SLA custom field from users and use per queue
  264
+    defaults, just revoke SeeCustomField and ModifyCustomField.
  265
+
  266
+    If you want people to see the current service level ticket is assigned
  267
+    to then grant SeeCustomField right.
  268
+
  269
+    You may want to allow customers or managers to escalate thier tickets.
  270
+    Just grant them ModifyCustomField right.
  271
+
  272
+REPORTING
  273
+    Since version 0.06 extension supports reporting. It works only with RT
  274
+    4.0+. Reports accessible in the UI. Each ticket has 'SLA Report' element
  275
+    in the page menu under 'Actions'. Search results also has element in the
  276
+    menu under 'Feeds'. Also, 'SLA Reports' are under 'Tools' in the main
  277
+    menu. This interface is protected by a new right 'SeeSLAReports'.
  278
+
  279
+    For purpose of statistics actors are splitted into three groups:
  280
+    requestors, owner and other. Any user who was not requestor or owner at
  281
+    the moment is assigned to 'other group'.
  282
+
  283
+    All time intervals are calculated in business hours.
  284
+
  285
+    The following statistics are collected:
  286
+
  287
+    *   count of replies splitted by above groups
  288
+
  289
+    *   first response to requestor
  290
+
  291
+        Tickets where first act was not by requestor are ignored. This
  292
+        happens when somebody on the staff created ticket for client.
  293
+
  294
+    *   response times
  295
+
  296
+    *   number of deadlines met
  297
+
  298
+    *   failed deadlines and timing of such
  299
+
  300
+    Note that changing configuration changes stats.
  301
+
  302
+TODO and CAVEATS
  303
+        * [not implemented] KeepInLoop and Response deadlines need adjusting. For example
  304
+          KeepInLoop is 2h and Response is 2h as well. Owner replies at point 0, deadline
  305
+          is 2h, at 1h requestor replies with anything -> deadline is moved according to
  306
+          response deadline to 3h when it must stay at 2h waiting for KeepInLoop follow up
  307
+          from owner and then move to another KeepInLoop deadline at 4h.
  308
+
  309
+        * [not implemented] Manually entered Due date should be treated as Resolve deadline.
  310
+          We should store it and use later, so this module can be used for projects. For
  311
+          example: Response 4 hours, KeepInLoop 1 day, Resolve 5 b.days; these are defaults,
  312
+          but any manual change to Due date changes Resolve deadline.
  313
+
  314
+        * [not implemented] WebUI
  315
+
  316
+        * [implemented, TODO: tests for options in the config] default SLA for queues
  317
+
  318
+        * [implemented, TODO: tests] add support for multiple b-hours definitions,
  319
+          this could be very helpfull when you have 24/7 mixed with 8/5 and/or
  320
+          something like 8/5+4/2 for different tickets(by requestor, queue or
  321
+          something else). So people would be able to handle tickets in the right
  322
+          order using Due dates.
  323
+
  324
+DESIGN
  325
+  Classes
  326
+    Actions are subclasses of RT::Action::SLA class that is subclass of
  327
+    RT::Extension::SLA and RT::Action classes.
  328
+
  329
+    Conditions are subclasses of RT::Condition::SLA class that is subclass
  330
+    of RT::Extension::SLA and RT::Condition classes.
  331
+
  332
+    RT::Extension::SLA is a base class for all classes in the extension, it
  333
+    provides access to config, generates Business::Hours objects, and other
  334
+    things useful for whole extension. As this class is the base for all
  335
+    actions and conditions then we MUST avoid adding methods which overload
  336
+    methods in 'RT::{Condition,Action}' RT's modules.
  337
+
  338
+NOTES
  339
+    If you run "make initdb" more than once you will create multiple SLA
  340
+    CFs. You can remove these via RT's "Configuration->Global" menu, (both
  341
+    Custom Fields and Scrips).
  342
+
  343
+AUTHOR
  344
+    Ruslan Zakirov <ruz@bestpractical.com>
  345
+
  346
+COPYRIGHT
  347
+    This extension is Copyright (C) 2007-2009 Best Practical Solutions, LLC.
  348
+
  349
+    It is freely redistributable under the terms of version 2 of the GNU
  350
+    GPL.
  351
+
138  inc/Module/Install/ReadmeFromPod.pm
... ...
@@ -0,0 +1,138 @@
  1
+#line 1
  2
+package Module::Install::ReadmeFromPod;
  3
+
  4
+use 5.006;
  5
+use strict;
  6
+use warnings;
  7
+use base qw(Module::Install::Base);
  8
+use vars qw($VERSION);
  9
+
  10
+$VERSION = '0.18';
  11
+
  12
+sub readme_from {
  13
+  my $self = shift;
  14
+  return unless $self->is_admin;
  15
+
  16
+  # Input file
  17
+  my $in_file  = shift || $self->_all_from
  18
+    or die "Can't determine file to make readme_from";
  19
+
  20
+  # Get optional arguments
  21
+  my ($clean, $format, $out_file, $options);
  22
+  my $args = shift;
  23
+  if ( ref $args ) {
  24
+    # Arguments are in a hashref
  25
+    if ( ref($args) ne 'HASH' ) {
  26
+      die "Expected a hashref but got a ".ref($args)."\n";
  27
+    } else {
  28
+      $clean    = $args->{'clean'};
  29
+      $format   = $args->{'format'};
  30
+      $out_file = $args->{'output_file'};
  31
+      $options  = $args->{'options'};
  32
+    }
  33
+  } else {
  34
+    # Arguments are in a list
  35
+    $clean    = $args;
  36
+    $format   = shift;
  37
+    $out_file = shift;
  38
+    $options  = \@_;
  39
+  }
  40
+
  41
+  # Default values;
  42
+  $clean  ||= 0;
  43
+  $format ||= 'txt';
  44
+
  45
+  # Generate README
  46
+  print "readme_from $in_file to $format\n";
  47
+  if ($format =~ m/te?xt/) {
  48
+    $out_file = $self->_readme_txt($in_file, $out_file, $options);
  49
+  } elsif ($format =~ m/html?/) {
  50
+    $out_file = $self->_readme_htm($in_file, $out_file, $options);
  51
+  } elsif ($format eq 'man') {
  52
+    $out_file = $self->_readme_man($in_file, $out_file, $options);
  53
+  } elsif ($format eq 'pdf') {
  54
+    $out_file = $self->_readme_pdf($in_file, $out_file, $options);
  55
+  }
  56
+
  57
+  if ($clean) {
  58
+    $self->clean_files($out_file);
  59
+  }
  60
+
  61
+  return 1;
  62
+}
  63
+
  64
+
  65
+sub _readme_txt {
  66
+  my ($self, $in_file, $out_file, $options) = @_;
  67
+  $out_file ||= 'README';
  68
+  require Pod::Text;
  69
+  my $parser = Pod::Text->new( @$options );
  70
+  open my $out_fh, '>', $out_file or die "Could not write file $out_file:\n$!\n";
  71
+  $parser->output_fh( *$out_fh );
  72
+  $parser->parse_file( $in_file );
  73
+  close $out_fh;
  74
+  return $out_file;
  75
+}
  76
+
  77
+
  78
+sub _readme_htm {
  79
+  my ($self, $in_file, $out_file, $options) = @_;
  80
+  $out_file ||= 'README.htm';
  81
+  require Pod::Html;
  82
+  Pod::Html::pod2html(
  83
+    "--infile=$in_file",
  84
+    "--outfile=$out_file",
  85
+    @$options,
  86
+  );
  87
+  # Remove temporary files if needed
  88
+  for my $file ('pod2htmd.tmp', 'pod2htmi.tmp') {
  89
+    if (-e $file) {
  90
+      unlink $file or warn "Warning: Could not remove file '$file'.\n$!\n";
  91
+    }
  92
+  }
  93
+  return $out_file;
  94
+}
  95
+
  96
+
  97
+sub _readme_man {
  98
+  my ($self, $in_file, $out_file, $options) = @_;
  99
+  $out_file ||= 'README.1';
  100
+  require Pod::Man;
  101
+  my $parser = Pod::Man->new( @$options );
  102
+  $parser->parse_from_file($in_file, $out_file);
  103
+  return $out_file;
  104
+}
  105
+
  106
+
  107
+sub _readme_pdf {
  108
+  my ($self, $in_file, $out_file, $options) = @_;
  109
+  $out_file ||= 'README.pdf';
  110
+  eval { require App::pod2pdf; }
  111
+    or die "Could not generate $out_file because pod2pdf could not be found\n";
  112
+  my $parser = App::pod2pdf->new( @$options );
  113
+  $parser->parse_from_file($in_file);
  114
+  open my $out_fh, '>', $out_file or die "Could not write file $out_file:\n$!\n";
  115
+  select $out_fh;
  116
+  $parser->output;
  117
+  select STDOUT;
  118
+  close $out_fh;
  119
+  return $out_file;
  120
+}
  121
+
  122
+
  123
+sub _all_from {
  124
+  my $self = shift;
  125
+  return unless $self->admin->{extensions};
  126
+  my ($metadata) = grep {
  127
+    ref($_) eq 'Module::Install::Metadata';
  128
+  } @{$self->admin->{extensions}};
  129
+  return unless $metadata;
  130
+  return $metadata->{values}{all_from} || '';
  131
+}
  132
+
  133
+'Readme!';
  134
+
  135
+__END__
  136
+
  137
+#line 254
  138
+

0 notes on commit 7323bbf

Please sign in to comment.
Something went wrong with that request. Please try again.