Statistiques des commentaires en ligne Confluence

Comment collecter des statistiques de commentaire de page dans Confluence?

Oui, et pourquoi cela pourrait-il ĂȘtre nĂ©cessaire?



Pourquoi et pourquoi


Dans le projet dans lequel je suis arrivé à travailler, le processus suivant de préparation et d'approbation des exigences est né:

  • Confluence a Ă©tĂ© utilisĂ© pour la documentation.
  • L'Ă©quipe des fournisseurs a prĂ©parĂ© des descriptions des processus commerciaux et de leurs Ă©tapes sous forme de pages sĂ©parĂ©es selon le modĂšle.
  • Une fois par semaine, un lot de descriptions prĂ©parĂ©es a Ă©tĂ© remis au client pour relecture.
  • L'Ă©quipe client a laissĂ© toutes les questions et ajouts sur la page correspondante sous la forme de commentaires intĂ©grĂ©s.
  • Et l'Ă©quipe du fournisseur a complĂ©tĂ© le contenu, rĂ©pondu aux commentaires, fixĂ© des questions pour une Ă©tude supplĂ©mentaire.
  • Si la question est rĂ©pondue et que le contenu est mis Ă  jour ou que la tĂąche Ă  Ă©tudier est fixĂ©e, l'Ă©quipe client doit fermer le commentaire.
  • Les questions sur un lot de documents de la semaine en cours doivent ĂȘtre fermĂ©es au moment du transfert du prochain lot la semaine prochaine.

Chaque semaine, les chefs de projet ont pour tĂąche de comprendre dans quelle mesure le lot de documents livrĂ© a fonctionnĂ© et ce qui peut ĂȘtre considĂ©rĂ© comme conditionnellement prĂȘt Ă  partir de celui-ci. Et les participants de l'Ă©quipe des fournisseurs doivent pĂ©riodiquement vĂ©rifier l'Ă©tat des commentaires sur leurs documents et travailler de maniĂšre ciblĂ©e avec les autres. Mais comment les trouver? Vous pouvez ouvrir chaque page, rechercher le premier commentaire dessus avec vos yeux (ou Ă  l'aide d'une petite astuce ), puis cliquer sur tous les commentaires, car l'Ă©quipe du client n'est pas pressĂ©e de les fermer, de penser Ă  chacun et de chercher une rĂ©ponse.

Le lot hebdomadaire contient de 50 Ă  100 pages distinctes, et le faire avec vos mains reprĂ©sente un travail considĂ©rable. Et si vous essayez toujours de recueillir des arguments pour convaincre l'autre cĂŽtĂ©, cela devient trĂšs triste. Et il y a aussi des commentaires qui rĂ©sultent d'une modification incorrecte de la page lorsque le texte source est accidentellement supprimĂ©. Un tel commentaire est visible dans les commentaires rĂ©solus, mais il ne peut pas ĂȘtre rouvert (vous pouvez, si vous recrĂ©ez un marqueur invisible dans le texte de la page).

La recherche d'outils a Ă©chouĂ©. De plus, Confluence est dĂ©ployĂ© cĂŽtĂ© client, vous ne pouvez pas installer de plugins, sans parler d’acheter. Aucune expertise en dĂ©veloppement macro.

À un moment donnĂ©, je me suis souvenu de l' API REST Confluence et de mon expĂ©rience antĂ©rieure avec l'utilisation de l' API Jira similaire. La recherche et les expĂ©riences avec les fonctions d'appel Ă  partir du navigateur ont montrĂ© que vous pouvez accĂ©der aux commentaires et Ă  leurs propriĂ©tĂ©s. Ensuite, vous avez dĂ» choisir un outil d'automatisation et vous pouvez commencer Ă  rĂ©soudre. J'ai une certaine expĂ©rience dans la crĂ©ation de scripts sur des outils plus susceptibles d'ĂȘtre plus proches d'administrateurs comme Bash, Perl, JScript. Je ne suis pas dĂ©veloppeur, je n'avais aucun outil familier ou familier. Je voulais essayer quelque chose de plus commun ou convenable. Ensuite, j'ai dĂ©couvert un wrapper pour l'API Python et j'ai dĂ©cidĂ© d'essayer avec.

Principe général


Le problÚme a été formulé comme suit. Vous devez trouver toutes les pages liées à une livraison hebdomadaire spécifique. Recueillez les commentaires sur eux dans une liste: page, lien vers le commentaire, auteur et date du commentaire, texte source sur la page, commentaire et réponses, auteur et date de la derniÚre réponse, statut du commentaire. En outre, collectez des statistiques pour chaque page, le nombre de commentaires au total, le nombre de dangers, le nombre de messages ouverts. Enregistrez tout sur une page de statistiques spéciale.

Je mets Python, regarde à travers les bases de travailler avec, et allons-y. Créez d'abord une connexion:

from atlassian import Confluence UserLogin = 'xxxxxx' # input("Login: ") UserPwd = 'xxxxxx' # input("Password: ") confluenceURL = 'http://wiki.xxxxxx' confluence = Confluence( url=confluenceURL, username=UserLogin, password=UserPwd) 

Pour rechercher des pages d'un lot hebdomadaire, j'ai décidé d'utiliser des balises. Comment les livrer en masse est une tùche distincte.

 page_label = 'week123' cql = 'space.key={} and label = "{}" and type = page ' 'ORDER BY title '.format('YYY', page_label) pages = confluence.cql(cql, expand=None, start=0, limit=200) 

Nous avons donc obtenu une liste de pages à vérifier. Ensuite, nous commençons à traiter chaque page individuelle. Nous collectons à partir de ses données sur les commentaires avec leurs paramÚtres. Sur la base de ces données, nous construisons des statistiques sur le nombre de commentaires sur la page et dans quelles conditions. Ensuite, nous supprimons le résultat pour toutes les pages et commençons à formater le résultat. Nous créons le corps de la page avec le résultat sous la forme d'un tableau de statistiques et d'une liste détaillée des commentaires ouverts.

Traitement de la liste des pages
 statistics = [] open_comments = [] #  - ? if pages is not None: #    ? if pages['size'] > 0: for page in pages['results']: print(page['title']) #       , #  page_comments = page_comments_data(page['content']['id']) #     statistics.append(page_statistics(page_comments)) #        #    . for comment in page_comments: if comment['Result'] not in ['resolved', 'nocomment']: if not (comment['Result'] == 'dangling' and comment['Author'] in excludeNames): open_comments.append(comment) #       . statistics.append(total_statistics(statistics)) #     page_id = confluence.get_page_id(space='YYY', title=page_title) #     ,    . page_body = ('<p><ac:structured-macro ac:name="toc" ac:schema-version="1"' '/></p>' '<h1>Comments Statistics</h1>{}' '<h1>Open Comments List</h1>{}' ).format(create_table(statistics), create_table(open_comments)) #  if page_id is not None: status = confluence.update_page( page_id=page_id, title=page_title, body=page_body, representation='storage' ) 

Fonctionnalités de l'API


Voyons maintenant comment traiter la page. Par dĂ©faut, l'API renvoie uniquement des informations de base, telles qu'un identifiant ou un nom de page. Toutes les propriĂ©tĂ©s supplĂ©mentaires doivent ĂȘtre spĂ©cifiĂ©es explicitement. Ils peuvent ĂȘtre vus en analysant le rĂ©sultat de l'appel. Des donnĂ©es supplĂ©mentaires peuvent ĂȘtre trouvĂ©es dans les sections ou sous-sections de _expandable. Nous ajoutons l'Ă©lĂ©ment souhaitĂ© pour dĂ©velopper et regarder plus loin jusqu'Ă  ce que nous trouvions les donnĂ©es nĂ©cessaires.

Exemple de problĂšme
 http://wiki.xxxxxx/rest/api/content/101743895?expand=body,children.comment { "id": "97517865", "type": "page", "status": "current", "title": "w2019-47 comments status", "children": { "comment": { "results": [], "start": 0, "limit": 25, "size": 25, "_links": {} }, "_links": {}, "_expandable": { "attachment": "/rest/api/content/97517865/child/attachment", "page": "/rest/api/content/97517865/child/page" } }, "body": { "_expandable": { "editor": "", "view": "", "export_view": "", "styled_view": "", "storage": "", "anonymous_export_view": "" } }, "extensions": { "position": "none" }, "_links": {}, "_expandable": { "metadata": "", "operations": "", "restrictions": "/rest/api/content/97517865/restriction/byOperation", "history": "/rest/api/content/97517865/history", "ancestors": "", "version": "", "descendants": "/rest/api/content/97517865/descendant", } } 

Et il y a aussi une restriction sur le nombre de rĂ©sultats publiĂ©s, la pagination. Il est configurĂ© cĂŽtĂ© serveur (API?) Et dans notre cas, il est de 25. Pour certaines demandes, il peut ĂȘtre modifiĂ© en spĂ©cifiant explicitement, mais cela ne fonctionnera que pour le niveau supĂ©rieur. Et rĂ©vĂ©lant des commentaires sur la page, nous n'en obtenons tout de mĂȘme que 25, tandis que la taille est Ă©galement fausse. Dans l'exemple, il y en avait 29 en rĂ©alitĂ©. Nous avons rĂ©ussi Ă  contourner la pagination des commentaires en utilisant une fonction distincte dans le module Confluence - get_page_comments avec la possibilitĂ© de spĂ©cifier la taille de la page.

 #    import re #      XML def replace_chars2(in_text): text = re.sub(r'&', '&', in_text) text = re.sub(r'\'', ''', text) text = re.sub(r'<', '<', text) text = re.sub(r'>', '>', text) text = re.sub(r'"', '"', text) return text 

Le prochain Ă©cueil attendait les caractĂ©ristiques de la prĂ©servation et de la dĂ©livrance des caractĂšres spĂ©ciaux. Le corps d'une page ou d'un commentaire peut ĂȘtre obtenu dans plusieurs vues: XML interne - stockage, HTML intermĂ©diaire sans sortie macro - vue et HTML avec sortie macro - export_view. Mais le titre du titre de la page et le texte original commentĂ© originalSelection sont toujours publiĂ©s sous une forme adaptĂ©e Ă  la lecture. Parce que Ă  l'avenir, ces donnĂ©es tombent dans le corps de la page avec des statistiques, puis certains caractĂšres ont conduit Ă  des erreurs de conversion. J'ai dĂ» Ă©crire une procĂ©dure de remplacement ci-dessus.

Commentaires sur la page


Passons maintenant Ă  l'analyse de la page. Il s'agit d'une procĂ©dure qui charge une page, collecte ses donnĂ©es, puis extrait une liste de commentaires avec des rĂ©ponses et les recueille dans la correspondance. Le rĂ©sultat est une liste de dictionnaires, oĂč chaque Ă©lĂ©ment de la liste correspond Ă  un commentaire avec des rĂ©ponses. Et tous les attributs de ce commentaire se trouvent dans les champs correspondants du dictionnaire.

Traitement d'une seule page
 def page_comments_data(page_identifier): #,      ,  , #   , #  ,    ,    #   (). expand_text = ('body.storage,extensions.inlineProperties' ',extensions.resolution,version,children.comment' ',children.comment.version,children.comment.body.storage' ) #    .     . conf_page = confluence.get_page_by_id(page_identifier, expand='body.storage') page_title = replace_chars2(conf_page['title']) #   . link_base = conf_page['_links']['base'] page_link = link_base + conf_page['_links']['webui'] page_code = '<a href="{}">{}</a>'.format(page_link, page_title) #   page_comments = confluence.get_page_comments(content_id=page_identifier, start=0, limit=1000, comments = [] #    for comment in page_comments['results']: #      - . if comment['extensions']['location'] == 'footer': continue #  comment_text = comment['body']['storage']['value'] comment_result = comment['extensions']['resolution']['status'] comment_link = '<a href="{}">{}</a>'\ .format(link_base + comment['_links']['webui'], 'link') #  comment_link = re.sub(r'&focusedCommentId=', '&focusedCommentId=', comment_link) #  created_when = re.sub(r'\.000\+', ' GMT+', re.sub(r'T', ' ', comment['version']['when'])) created_by = comment['version']['by']['displayName'] orig_text = replace_chars2(comment['extensions'] ['inlineProperties']['originalSelection']) #    /. thread = '<b>To text: </b>{}<br/><b>At: </b>{}<br/><b>By: ' '</b>{}<br/>{}'.format(orig_text, created_when, created_by, comment_text) last_by = '' last_when = '' #  answers = comment['children']['comment']['size'] if answers > 0: for message in comment['children']['comment']['results']: #  last_when = re.sub(r'\.000\+', ' GMT+', re.sub(r'T', ' ', message['version']['when'])) last_by = message['version']['by']['displayName'] #  thread += ('<br/>===next===<br/><b>At: </b>{}<br/><b>By: ' '</b>{}<br/>{}'.format(last_when, last_by, message['body']['storage']['value']) ) #  . row_comm = {"Page": page_code, "Comment": comment_link, "Thread": thread, "Result": comment_result, "Answers count": answers, "Creation Date": created_when, "Author": created_by, "Last Date": last_when, "Last Author": last_by} comments.append(row_comm) #   ,   , #     . if len(comments) == 0: row_comm = {"Page": page_code, "Comment": 'nolink', "Thread": 'nocomment', "Result": 'nocomment', "Answers count": 0, "Creation Date": 'never', "Author": 'nobody', "Last Date": 'never', "Last Author": 'nobody'} comments.append(row_comm) return comments 

Statistiques et tableaux


Pour la clarté de la présentation des données, nous collectons des statistiques à leur sujet et les organisons sous forme de tableaux.

Nous traitons les statistiques
 def page_statistics(comments_data): open_count = 0 dang_count = 0 comment_count = len(comments_data) if comment_count > 0: for comment in comments_data: #     if comment['Result'] not in ['resolved', 'nocomment']: if comment['Result'] in ['open', 'reopened']: open_count += 1 if comment['Result'] == 'dangling' and comment['Author'] not in excludeNames: dang_count += 1 res_dict = {'Page': comments_data[0]['Page'], 'Total': comment_count, 'Resolved': comment_count - open_count - dang_count, 'Dangling': dang_count, 'Open': open_count} return res_dict #   def total_statistics(stat_data): total_comment = 0 total_resolved = 0 total_open = 0 total_dangling = 0 for statRow in stat_data: total_comment += statRow['Total'] total_resolved += statRow['Resolved'] total_open += statRow['Open'] total_dangling += statRow['Dangling'] res_dict = {'Page': 'All Pages Total', 'Type': '', 'Jira': '', 'Status': '', 'Total': total_comment, 'Resolved': total_resolved, 'Dangling': total_dangling, 'Open': total_open} return res_dict 

Pour faire les donnĂ©es sous forme de tableaux, nous ferons une autre procĂ©dure. Il forme le code HTML du tableau Ă  partir de la liste des dictionnaires, comme en-tĂȘtes il ajoute une ligne Ă  partir des noms des clĂ©s du dictionnaire et ajoute une colonne avec des numĂ©ros de ligne.

 def create_table(tab_data): tab_start = '<table style="width: 100.00%;"><colgroup><col />' '<col /></colgroup><tbody>' tab_end = '</tbody></table>' tab_code = tab_start + '<tr>' row_num = 1 if len(tab_data) > 0: tab_code += '<th>Num</th>' for key in tab_data[0].keys(): tab_code += '<th>{}</th>'.format(key) tab_code += '</tr>' for row in tab_data: tab_code += '<tr><td>{}</td>'.format(row_num) row_num += 1 for field in row.values(): tab_code += '<td>{}</td>'.format(field) tab_code += '</tr>' tab_code += tab_end + '\n' return tab_code 

Maintenant, tout est prĂȘt. AprĂšs avoir Ă©tiquetĂ© et exĂ©cutĂ© le script, nous obtenons une page qui ressemble Ă  ceci:



PS

Bien sûr, ce n'est pas tout ce qui s'est finalement avéré. Il y a eu une analyse des pages et une recherche d'une macro avec le numéro de tùche dans Jira. Il y avait un étiquetage automatique par numéros de tùches dans Jira et des liens entre eux et Confluence. Il y a eu une comparaison et une vérification des listes de livraison hebdomadaires. Il y avait une sauvegarde des commentaires dans Excel et la collecte de données communes à partir de plusieurs fichiers Excel hebdomadaires. Et récemment, l'analyse des commentaires de Word a été ajoutée.

Source: https://habr.com/ru/post/fr485652/


All Articles