对于一个人数30-50的创业团队来说,如何让每个成员及时了解产品的变化是一个有意思的话题
为什么让产品信息共享?
因为,团队不同的 team 的工作侧重点各有不同:
- Support Team 关注用户使用产品时出现的问题,及时掌握产品变化,可以让 Support Team 的工作有预先准备,给用户提供最新指导,工作效率更高。
- Growth Team 关注产品的价值,旧功能的优化和新功能的上线都会影响他们的决策,例如 A/B Test,QA 等。
- Develop Team 关注产品的功能实现,别人代码的更新可能会影响自己的开发。
“变化”的分类:
- 大变化:可以理解为milestone (里程碑),比如大功能的上线。
- 这种大的变化适合专门的文档去记录,比如产品更新Blog:简单明了的文字、截图、视频教程等。
- 小变化:
- 用户看得见的变化:小的UI优化,Bug修复。
- 用户体验的变化:性能的优化(更加流畅),交互的变化(更加合理)。
解决思路
第一个问题,如何搜集变化?
- 方案一:成员每日写工作总结。
- 根据工作内容抽离出“变化”。
- 有个成员专门去维护一个或一系列文档。
- 优点:系统性强。
- 缺点:人力成本高,适合有一定规模的团队。
- 方案二:可以维护一个 wiki。
- 每个成员主动将“变化”写入文档中去。
- 优点:调动成员的积极性。
- 缺点:
- 需要一些工具,如文档协作,存储与检索。
- 需要一些规范和 review。
- 方案三:利用第三方工具 (github) 自动搜集。
- 检索 Pull Request 中详细描述这次PR的内容,搜集起来。
- 优点:节省人力,有利于PR规范化。
- 缺点:需要相关开发工作。
我们 Strikingly 崇尚敏捷,高效和自动化。采用了方案三。Develop Team 使用 Pull Request 模板,每天将特殊的 Pull Request 搜集起来。
第二个问题,如何通知每个成员?
- 发消息:“去看文档的更新”
- 优点:可以融入我们的日常交流中去。
- 缺点:消息流很容易丢失在其他讨论中
- 发邮件:将产品的“变化”写进邮件里面。
- 优点:比较正式,可自定义HTML,样式可控制,便于给归档。
- 缺点:需要相关开发工作。
我们决定采用邮件的方式,每天发送一个以邮件的形式通知团队的每个人。
结合起来后大致分成7个步骤:
- 设置Github
- 约定PR模板
- 记录部署时间
- 处理 github webhook
- 数据存储到 Redis
- 准备邮件
- 循环发邮件
开发配置相关:
- Rails 后端
- Redis 临时存储
- github 的 webhook 服务
- 用到的一些 gem:
- git_api: 访问 github API
- maruku: 将 Markdown 转换为 HTML
- sidekiq/sidetiq: 后台循环发送邮件
Step 1: 设置 Github
- 项目的 settings 中设置 webhook
- 设置 Payload URL
- 设置 events 类型:勾选 Pull Request
- 添加 Personal access token
- 使用 gem github_api, 添加配置文件
Step 2: 约定 PR 模板
示例 PR 模板:
模板的重要性:
- 规范 Develop Team 的开发流程。
- 如果不使用模板,我们开发的这个工具,没有任何卵用O__O"…
Step 3: 记录部署时间
由于有些PR虽然被merge了,但并不一定被部署了,所以我们需要将最近一次部署的时间记录下来。
class DeployLogger
DEPLOY_KEY = 'AWESOME_REPO_LAST_DEPLOY_KEY'
# 根据实际情况使用默认时间
def self.deploy_at
$redis.get(DEPLOY_KEY) || '2015-08-10T02:27:38Z'
end
def self.deploy_at= time
$redis.set DEPLOY_KEY, time
$redis.expire DEPLOY_KEY, 60.days
end
end
Step 4: 处理 github webhook
- 获取 PR 的 Number
- 因为我们并不能相信 webhook,所以我们应该基于 webhook 的信息主动通过 github API 获取数据。
- 筛选PR: merged 到指定分支(develop) 且含有 - [x] contains user facing changes
- 整合信息,提取出 PR 的Number,title,body,获取 PR 提交username等。
Step 5: 存储到 Redis
class PrDescription
LAST_SEND_EMAIL_KEY = 'LAST_SEND_EMAIL_KEY'
# 使用 redis ordered set
# merged_at 时间秒数作为 score
def self.create columns = {}
key_word = columns[:key_word]
merged_at = columns[:merged_at]
content = columns[:content]
$redis.zadd key_word, Time.parse(merged_at).to_i, content
$redis.expire key_word, 2.days
end
def self.count_of key_word
$redis.zcount key_word, '-inf', '+inf'
end
# 根据情况做适当调整
def self.last_send_email_at
$redis.get(LAST_SEND_EMAIL_KEY) || '2015-08-10T02:27:38Z'
end
# 根据情况做适当调整
def self.last_send_email_at= time
$redis.set LAST_SEND_EMAIL_KEY, time
$redis.expire LAST_SEND_EMAIL_KEY, 5.days
end
# 获取当前部署好的PR
# 范围:最近的一次发邮件的时间到最近的一次部署时间
def self.descriptions_need_to_send_for key_word
from_time_score = Time.parse(self.last_send_email_at).to_i + 0.01
to_time_score = Time.parse(DeployLogger.deploy_at).to_i
$redis.zrangebyscore key_word, from_time_score, to_time_score
end
end
Step 6: 准备邮件
# 在每个 PrDescription 之间要用"\n"隔开,注意是双引号,需要转义,不能加空格,会影响 Markdown 的转化
markdown_string = PrDescription.descriptions_need_to_send_for(USER_FACING_CHANGES_KEY).join("\n")
if markdown_string.present?
# 使用gem maruku 将Markdown
# 有些小问题:
# 1. 需要将 " 转换成 '
# 2. 去除 '\n'
@content = Maruku.new(markdown_string).to_html.gsub(/\"/,"'").gsub(/\n/, '').html_safe
mail subject: "User Facing Changes #{Date.today}", from: PRODUCT_EMAIL, to: TEAM_EMAIL
# 设置最后一次发邮件的时间
PrDescription.last_send_email_at = DeployLogger.deploy_at
end
Step 7: 循环发邮件
# 用到 gem: sidekiq 和 sidetiq
class SendToSupportTeamChangesEmail
include Sidekiq::Worker
include Sidetiq::Schedulable
sidekiq_options :queue => :default, :backtrace => true, :retry => 2
# everyday 8:30 AM
# 请注意时区,可能需要转换
recurrence { daily.hour_of_day(8).minute_of_hour(30) }
def perform
# 负责发送内部邮件的类
InternalMailer.user_facing_change_email.deliver
end
end
最后,如果今天有PR
merged 到 develop 分支并且还被部署到 production 环境,我们明天一早会受到一封邮件:
这方面的探索还在继续,思路和代码还有可多地方可以提升,请各位多多指教。