Estatísticas de comentários em linha do Confluence

Como coletar estatísticas de comentários da página no Confluence?

Sim, e por que isso pode ser necessário?



Porque e porque


No projeto em que trabalhei, nasceu o seguinte processo de preparação e aprovação de requisitos:

  • Confluência foi usada para documentação.
  • A equipe do fornecedor preparou descrições dos processos de negócios e suas etapas na forma de páginas separadas, de acordo com o modelo.
  • Uma vez por semana, um lote de descrições preparadas era entregue ao cliente para revisão.
  • A equipe do cliente deixou todas as perguntas e complementos na página correspondente na forma de comentários internos.
  • E a equipe do fornecedor complementou o conteúdo, respondeu a comentários, corrigiu perguntas para estudos adicionais.
  • Se a pergunta for respondida e o conteúdo for atualizado ou a tarefa de estudo for corrigida, a equipe do cliente deve fechar o comentário.
  • As perguntas sobre um lote de documentos da semana atual devem ser encerradas quando o próximo lote for transferido na próxima semana.

A cada semana, os gerentes de projeto têm a tarefa de entender o quanto o lote de documentos entregue deu certo e o que pode ser considerado condicionalmente pronto. E os participantes da equipe de fornecedores periodicamente precisam verificar o status dos comentários em seus documentos e trabalhar propositadamente com o restante. Mas como encontrá-los? Você pode abrir cada página, procurar o primeiro comentário com seus olhos (ou com a ajuda de um pequeno truque ) e clicar em todos os comentários, porque a equipe do cliente não tem pressa em fechá-los, pense em cada um e verifique se há uma resposta.

O lote semanal contém de 50 a 100 páginas separadas, e fazer isso com as mãos é uma quantidade significativa de trabalho. E se você ainda tenta coletar argumentos para convencer o outro lado, fica muito triste. E também há comentários pendentes que resultam da edição imprecisa da página quando o texto de origem é excluído acidentalmente. Esse comentário é visível nos resolvidos, mas não pode ser reaberto (você pode, se recriar um marcador invisível no texto da página).

A busca por ferramentas não teve êxito. Além disso, o Confluence é implantado no lado do cliente, você não pode instalar plug-ins, para não mencionar a compra. Nenhuma experiência em desenvolvimento de macro.

Em algum momento, lembrei-me da API REST do Confluence e da experiência anterior com o uso da API Jira semelhante. Pesquisa e experimentos com funções de chamada no navegador mostraram que você pode acessar os comentários e suas propriedades. Em seguida, você teve que escolher uma ferramenta para automação e pode começar a resolver. Tenho alguma experiência na criação de scripts em ferramentas com maior probabilidade de estarem mais próximas de administradores como Bash, Perl, JScript. Não sou desenvolvedor, não tenho ferramentas familiares ou familiares. Eu queria tentar algo mais comum ou adequado. Descobri um wrapper para a API Python e decidi tentar com ele.

Princípio geral


O problema foi formulado da seguinte maneira. Você precisa encontrar todas as páginas relacionadas a uma entrega semanal específica. Colete comentários sobre eles em uma lista: página, link para comentar, autor e data do comentário, texto-fonte na página, comentário e respostas, autor e data da última resposta, status do comentário. Além disso, colete estatísticas para cada página, quantos comentários no total, quantos perigos, quantos estão abertos. Salve tudo em uma página de estatísticas especiais.

Coloquei o Python, analise o básico de como trabalhar com ele e vamos lá. Primeiro, crie uma conexão:

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

Para procurar páginas de um lote semanal, decidi usar tags. Como entregá-los em massa é uma tarefa separada.

 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) 

Então, temos uma lista de páginas para verificar. Em seguida, começamos a processar cada página individual. Coletamos a partir dos dados dela nos comentários com seus parâmetros. Com base nesses dados, criamos estatísticas sobre quantos comentários existem na página e em que condições. Em seguida, eliminamos o resultado de todas as páginas e começamos a formatar o resultado. Criamos o corpo da página com o resultado na forma de uma tabela de estatísticas e uma lista detalhada de comentários abertos.

Processamento da lista de páginas
 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' ) 

Recursos da API


Agora vamos descobrir como processar a página. Por padrão, a API retorna apenas informações básicas, como um identificador ou nome da página. Todas as propriedades adicionais devem ser especificadas explicitamente. Eles podem ser vistos analisando o resultado da chamada. Dados adicionais podem ser encontrados em seções ou subseções de _expandable. Adicionamos o item desejado para expandir e procurar mais até encontrarmos os dados necessários.

Exemplo de problema
 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", } } 

E há também uma restrição no número de resultados emitidos, paginação. Ele está configurado no lado do servidor (API?) E, no nosso caso, é 25. Para algumas solicitações, pode ser alterado especificando explicitamente, mas funcionará apenas para o nível superior. E revelando comentários para a página, obtemos todos os mesmos apenas 25, enquanto o tamanho também é falso. No exemplo, havia 29 na realidade. Conseguimos contornar a paginação dos comentários usando uma função separada no módulo Confluence - get_page_comments com a capacidade de especificar o tamanho da página.

 #    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 

A próxima armadilha estava esperando nas características da preservação e emissão de caracteres especiais. O corpo de uma página ou comentário pode ser obtido em várias visualizações: XML interno - armazenamento, HTML intermediário sem saída de macro - exibição e HTML com saída de macro - export_view. Mas o título do título da página e o texto original comentado originalSelection sempre são publicados em um formato adequado para leitura. Porque no futuro, esses dados caem no corpo da página com estatísticas e, em seguida, alguns caracteres levam a erros de conversão. Eu tive que escrever um procedimento de substituição acima.

Comentários da página


Agora, para analisar a página. Esse é um procedimento que carrega uma página, coleta seus dados, extrai uma lista de comentários com respostas e os coleta em correspondência. O resultado é uma lista de dicionários, em que cada elemento da lista corresponde a um comentário com respostas. E todos os atributos deste comentário estão nos campos correspondentes do dicionário.

Processamento de página única
 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 

Estatísticas e tabelas


Para maior clareza da apresentação dos dados, coletamos estatísticas sobre elas e as organizamos na forma de tabelas.

Nós processamos estatísticas
 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 

Para tornar os dados na forma de tabelas, faremos mais um procedimento. Ele forma o código HTML da tabela a partir da lista de dicionários; como títulos, ele adiciona uma linha dos nomes das chaves do dicionário e adiciona uma coluna com números de linha.

 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 

Agora está tudo pronto. Após rotular e executar o script, obtemos uma página parecida com esta:



PS

Obviamente, isso não foi tudo o que aconteceu no final. Houve uma análise de páginas e uma pesquisa por uma macro com o número da tarefa em Jira. Havia uma marcação automática por números de tarefas em Jira e links deles para o Confluence. Houve uma comparação e verificação das listas de entrega semanais. Houve uma economia de comentários no Excel e a coleta de dados comuns de vários arquivos semanais do Excel. E recentemente analisando comentários do Word foi adicionado.

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


All Articles